2010-08-09 10 views
6

J'ai une table qui sort semblable à celui (bien en milliers):(Oracle) comment les lignes de groupe pour la pagination

 EMPNO ENAME  TRANDATE  AMT 
---------- ---------- --------- ------- 
     100 Alison  21-MAR-96 45000 
     100 Alison  12-DEC-78 23000 
     100 Alison  24-OCT-82 11000 
     101 Linda  15-JAN-84 16000 
     101 Linda  30-JUL-87 17000 
     102 Celia  31-DEC-90 78000 
     102 Celia  17-SEP-96 21000 
     103 James  21-MAR-96 45000 
     103 James  12-DEC-78 23000 
     103 James  24-OCT-82 11000 
     104 Robert  15-JAN-84 16000 
     104 Robert  30-JUL-87 17000 

Ma sortie désirée serait similaire à ceci:

 EMPNO ENAME  TRANDATE  AMT PAGE 
---------- ---------- --------- ------- ---- 
     100 Alison  21-MAR-96 45000 1 
     100 Alison  12-DEC-78 23000 1 
     100 Alison  24-OCT-82 11000 1 
     101 Linda  15-JAN-84 16000 2 
     101 Linda  30-JUL-87 17000 2 
     102 Celia  31-DEC-90 78000 2 
     102 Celia  17-SEP-96 21000 2 
     103 James  21-MAR-96 45000 3 
     104 Robert  12-DEC-78 23000 4 
     104 Robert  24-OCT-82 11000 4 
     104 Robert  15-JAN-84 16000 4 
     104 Robert  30-JUL-87 17000 4 

Fondamentalement, il devrait insérer un nouveau champ pour identifier la page à laquelle il appartient. Le saut de page est basé sur les lignes. Et, comme si "maintenu ensemble" dans EMPNO, il ajoute 1 à PAGE lorsque les lignes ne peuvent pas ajouter le prochain lot EMPNO. C'est pour la limite d'Excel puisque Excel n'autorise pas plus de 65000 lignes (ou plus) dans une seule feuille. Dans le cas de l'échantillon, il n'y a que 4 lignes. Le nombre limite est statique.

+0

Alors, pouvez-vous garantir qu'aucun EMPNO aura> 65000 dossiers? – APC

+0

En outre, quelle version d'Excel utilisez-vous? Excel 2007 autorise un nombre fou de 1 048 576 lignes par feuille de calcul. – APC

+0

Eh bien, j'espérais que cela prendrait même en charge les versions d'Excel antérieures à 2007. (http://office.microsoft.com/fr-fr/excel-help/excel-specifications-and-limits-HP005199291.aspx) – keiko

Répondre

2

ThinkJet a raison de dire que certaines des autres réponses ne répondent pas à l'exigence de «garder ensemble». Cependant, je pense que cela peut être fait sans recourir à un agrégat défini par l'utilisateur.

Données d'échantillons

create table test (empno number, ename varchar2(20), trandate date, amt number); 
insert into test values (100, 'Alison' , to_date('21-MAR-1996') , 45000); 
insert into test values (100, 'Alison' , to_date('12-DEC-1978') , 23000); 
insert into test values (100, 'Alison' , to_date('24-OCT-1982') , 11000); 
insert into test values (101, 'Linda' , to_date('15-JAN-1984') , 16000); 
insert into test values (101, 'Linda' , to_date('30-JUL-1987') , 17000); 
insert into test values (102, 'Celia' , to_date('31-DEC-1990') , 78000); 
insert into test values (102, 'Celia' , to_date('17-SEP-1996') , 21000); 
insert into test values (103, 'James' , to_date('21-MAR-1996') , 45000); 
insert into test values (103, 'James' , to_date('12-DEC-1978') , 23000); 
insert into test values (103, 'James' , to_date('24-OCT-1982') , 11000); 
insert into test values (104, 'Robert' , to_date('15-JAN-1984') , 16000); 
insert into test values (104, 'Robert' , to_date('30-JUL-1987') , 17000); 

Maintenant, déterminer la ligne d'extrémité de chaque segment de empno (à l'aide du classement de trouver le début et la COUNT..PARTITION BY pour trouver le nombre dans le segment).

Ensuite, utilisez ceil/4 de la solution d'APC pour les regrouper dans leurs 'pages'. Encore une fois, comme l'a fait remarquer ThinkJet, il y a un problème dans la spécification car il ne répond pas à la situation lorsqu'il y a plus d'enregistrements dans le segment empno 'keep together' que dans une page.

select empno, ename, 
     ceil((rank() over (order by empno) + 
     count(1) over (partition by empno))/6) as chunk 
from test 
order by 1; 

Comme l'a souligné ThinkJet, cette solution est la preuve de balle.

drop table test purge; 

create table test (empno number, ename varchar2(20), trandate date, amt number); 
declare 
    cursor csr_name is 
    select rownum emp_id, 
      decode(rownum,1,'Alan',2,'Brian',3,'Clare',4,'David',5,'Edgar', 
      6,'Fred',7,'Greg',8,'Harry',9,'Imran',10,'John', 
      11,'Kevin',12,'Lewis',13,'Morris',14,'Nigel',15,'Oliver', 
      16,'Peter',17,'Quentin',18,'Richard',19,'Simon',20,'Terry', 
      21,'Uther',22,'Victor',23,'Wally',24,'Xander', 
      25,'Yasmin',26,'Zac') emp_name 
    from dual connect by level <= 26; 
begin 
    for c_name in csr_name loop 
    for i in 1..11 loop 
     insert into test values 
      (c_name.emp_id, c_name.emp_name, (date '2010-01-01') + i, 
      to_char(sysdate,'SS') * 1000); 
    end loop; 
    end loop; 
end; 
/

select chunk, count(*) 
from 
    (select empno, ename, 
     ceil((rank() over (order by empno) + 
     count(1) over (partition by empno))/25) as chunk 
    from test) 
group by chunk 
order by chunk 
; 

donc avec la taille de morceau de 25 et la taille du groupe de 11, nous obtenons les sauts où elle se situe 33 personnes dans le morceau, malgré la limite de 25. De grandes tailles de morceaux et de petits groupes devraient rendre cela peu fréquent, mais vous voudriez laisser une certaine marge de manœuvre. Alors peut-être définir les morceaux à 65 000 plutôt que d'aller jusqu'à 65 536.

+0

Merci beaucoup! Cela fonctionne pour mon cas! Et j'ai vraiment pensé que la requête serait plus longue. Je comprends le problème des enregistrements 65k + avec le même EMPNO, mais dans mon cas, cela ne se produirait probablement pas. – keiko

+1

Il y a la même erreur que dans les autres réponses: les écarts aléatoires à la fin de la page ne sont pas pris en compte dans le calcul. Par exemple. Supposons que vous ayez 3 groupes de rangées (A, B, C) avec 6 rangées dans chaque groupe. Essayez de le placer sur les pages, 6 rang par page: Groupe A - rank() = 1 - Ok, 1ère page; Groupe B - rang() = 7 - Ok, la deuxième page est remplie à partir du haut et 4 lignes sont laissées en blanc sur la première page; Groupe C - rank() = 13 - ERR !, semble correspondre à la deuxième page mais doit être placé sur la troisième, car la valeur REAL rank() doit inclure les lignes vides de la première page: 6 + 4 + 6 + 1 = 17. Aucun moyen de prédire le nombre de lignes vides avant la construction de la page réelle. – ThinkJet

+0

Cela peut arriver même pour 65k + lignes, même à la fin de la 2ème page: E.g. 5 lignes libres sur la 1ère page + 4 lignes libres sur la 2ème page + le bloc suivant ont une taille de 5 à 9 lignes ... – ThinkJet

1

L'instruction SQL suivante divise les vingt enregistrements dans ma table EMP en cinq pages de quatre lignes chacune:

SQL> select empno 
    2   , ename 
    3   , deptno 
    4   , ceil((row_number() over (order by deptno, empno)/4)) as pageno 
    5 from emp 
    6/

    EMPNO ENAME   DEPTNO  PAGENO 
---------- ---------- ---------- ---------- 
     7782 BOEHMER   10   1 
     7839 SCHNEIDER   10   1 
     7934 KISHORE   10   1 
     7369 CLARKE    20   1 
     7566 ROBERTSON   20   2 
     7788 RIGBY    20   2 
     7876 KULASH    20   2 
     7902 GASPAROTTO   20   2 
     7499 VAN WIJK   30   3 
     7521 PADFIELD   30   3 
     7654 BILLINGTON   30   3 
     7698 SPENCER   30   3 
     7844 CAVE    30   4 
     7900 HALL    30   4 
     8083 KESTELYN   30   4 
     8084 LIRA    30   4 
     8060 VERREYNNE   50   5 
     8061 FEUERSTEIN   50   5 
     8085 TRICHLER   50   5 
     8100 PODER    50   5 

20 rows selected. 

SQL> 
+1

ne fonctionnera pas. Condition importante - cet empno ne peut pas être en deux groupes. Dans votre cas, empno 101 sera en deux groupes séparés. –

+0

Merci! Ce code fonctionne pour moi! (Mise à jour: Ok ... peut-être pas.Bonne trouvaille, Michael.) – keiko

+0

Juste ne fonctionne pas - mauvais résultats. Par exemple. où est compté des lignes vides supplémentaires à la fin de la page pour la 3ème page et plus tard? – ThinkJet

2

Il est trop difficile, voire impossible de faire une telle chose en SQL.

Mais avec quelques limitations, le problème peut être résolu avec l'aide de user-defined aggregate functions.

Tout d'abord, créer un objet avec la mise en œuvre d'interface ODCIAggregate:

create or replace type page_num_agg_type as object 
(
    -- Purpose : Pagination with "leave together" option 

    -- Attributes    

    -- Current page number 
    cur_page_number number,         

    -- Cumulative number of rows per page incremented by blocks 
    cur_page_row_count number, 

    -- Row-by-row counter for detect page overflow while placing single block 
    page_row_counter number, 

    -- Member functions and procedures 

    static function ODCIAggregateInitialize(
    sctx in out page_num_agg_type 
) 
    return number, 

    member function ODCIAggregateIterate(
    self  in out page_num_agg_type, 
    value  in  number 
) 
    return number, 

    member function ODCIAggregateTerminate(
    self  in page_num_agg_type, 
    returnValue out number, 
    flags  in number 
) 
    return number, 

    member function ODCIAggregateMerge(
    self in out page_num_agg_type, 
    ctx2 in  page_num_agg_type 
) 
    return number 

); 

Créer corps de type:

create or replace type body PAGE_NUM_AGG_TYPE is 

    -- Member procedures and functions 
    static function ODCIAggregateInitialize(
    sctx in out page_num_agg_type 
) 
    return number 
    is 
    begin 
     sctx := page_num_agg_type(1, 0, 0); 
     return ODCIConst.Success; 
    end; 

    member function ODCIAggregateIterate(
    self  in out page_num_agg_type, 
    value  in  number 
) 
    return number 
is 
    -- !!! WARNING: HARDCODED !!! 
    RowsPerPage number := 4; 
begin 

    self.page_row_counter := self.page_row_counter + 1; 

    -- Main operations: determine number of page 

    if(value > 0) then 
    -- First row of new block 

    if(self.cur_page_row_count + value > RowsPerPage) then 
     -- If we reach next page with new block of records - switch to next page. 
     self.cur_page_number := self.cur_page_number + 1; 
     self.cur_page_row_count := value; 
     self.page_row_counter := 1; 
    else 
     -- Just increment rows and continue to place on current page 
     self.cur_page_row_count := self.cur_page_row_count + value; 
    end if; 

    else      
    -- Row from previous block 

    if(self.page_row_counter > RowsPerPage) then 
     -- Single block of rows exceeds page size - wrap to next page. 
     self.cur_page_number := self.cur_page_number + 1; 
     self.cur_page_row_count := self.cur_page_row_count - RowsPerPage; 
     self.page_row_counter := 1; 
    end if; 

    end if; 

    return ODCIConst.Success; 
end; 

member function ODCIAggregateTerminate(
    self  in page_num_agg_type, 
    returnValue out number, 
    flags  in number 
) 
    return number 
is 
begin 
    -- Returns current page number as result 
    returnValue := self.cur_page_number; 
    return ODCIConst.Success; 
end; 

member function ODCIAggregateMerge(
    self in out page_num_agg_type, 
    ctx2 in  page_num_agg_type 

) 
    return number 
is 
begin 
    -- Can't act in parallel - error on merging attempts 
    raise_application_error(-20202,'PAGE_NUM_AGG_TYPE can''t act in parallel mode'); 
    return ODCIConst.Success; 
end; 

end; 

fonction Créer agrreation utiliser avec le type:

create function page_num_agg (
    input number 
) return number aggregate using page_num_agg_type; 

Suivant préparer les données et utiliser une nouvelle fonction pour calculer les numéros de page:

with data_list as (
    -- Your example data as source 
    select 100 as EmpNo, 'Alison' as EmpName, to_date('21-MAR-96','dd-mon-yy') as TranDate, 45000 as AMT from dual union all 
    select 100 as EmpNo, 'Alison' as EmpName, to_date('12-DEC-78','dd-mon-yy') as TranDate, 23000 as AMT from dual union all 
    select 100 as EmpNo, 'Alison' as EmpName, to_date('24-OCT-82','dd-mon-yy') as TranDate, 11000 as AMT from dual union all 
    select 101 as EmpNo, 'Linda' as EmpName, to_date('15-JAN-84','dd-mon-yy') as TranDate, 16000 as AMT from dual union all 
    select 101 as EmpNo, 'Linda' as EmpName, to_date('30-JUL-87','dd-mon-yy') as TranDate, 17000 as AMT from dual union all 
    select 102 as EmpNo, 'Celia' as EmpName, to_date('31-DEC-90','dd-mon-yy') as TranDate, 78000 as AMT from dual union all 
    select 102 as EmpNo, 'Celia' as EmpName, to_date('17-SEP-96','dd-mon-yy') as TranDate, 21000 as AMT from dual union all 
    select 103 as EmpNo, 'James' as EmpName, to_date('21-MAR-96','dd-mon-yy') as TranDate, 45000 as AMT from dual union all 
    select 103 as EmpNo, 'James' as EmpName, to_date('12-DEC-78','dd-mon-yy') as TranDate, 23000 as AMT from dual union all 
    select 103 as EmpNo, 'James' as EmpName, to_date('24-OCT-82','dd-mon-yy') as TranDate, 11000 as AMT from dual union all 
    select 104 as EmpNo, 'Robert' as EmpName, to_date('15-JAN-84','dd-mon-yy') as TranDate, 16000 as AMT from dual union all 
    select 104 as EmpNo, 'Robert' as EmpName, to_date('30-JUL-87','dd-mon-yy') as TranDate, 17000 as AMT from dual union all 
    select 105 as EmpNo, 'Monica' as EmpName, to_date('30-JUL-88','dd-mon-yy') as TranDate, 31000 as AMT from dual union all 
    select 105 as EmpNo, 'Monica' as EmpName, to_date('01-JUL-87','dd-mon-yy') as TranDate, 19000 as AMT from dual union all 
    select 105 as EmpNo, 'Monica' as EmpName, to_date('31-JAN-97','dd-mon-yy') as TranDate, 11000 as AMT from dual union all 
    select 105 as EmpNo, 'Monica' as EmpName, to_date('17-DEC-93','dd-mon-yy') as TranDate, 33000 as AMT from dual union all 
    select 105 as EmpNo, 'Monica' as EmpName, to_date('11-DEC-91','dd-mon-yy') as TranDate, 65000 as AMT from dual union all 
    select 105 as EmpNo, 'Monica' as EmpName, to_date('22-OCT-89','dd-mon-yy') as TranDate, 19000 as AMT from dual 
), 
ordered_data as (
    select    
    -- Source table fields 
    src_data.EmpNo, src_data.EmpName, src_data.TranDate, src_data.AMT, 
    -- Calculate row count per one employee 
    count(src_data.EmpNo) over(partition by src_data.EmpNo)as emp_row_count, 
    -- Calculate rank of row inside employee data sorted in output order 
    rank() over(partition by src_data.EmpNo order by src_data.EmpName, src_data.TranDate) as emp_rnk 
    from 
    data_list src_data 
) 
-- Final step: calculate page number for rows 
select 
    -- Source table data 
    ordered_data.EmpNo, ordered_data.EmpName, ordered_data.TranDate, ordered_data.AMT, 
    -- Aggregate all data with our new function 
    page_num_agg(
     -- pass count of rows to aggregate function only for first employee's row 
     decode(ordered_data.emp_rnk, 1, ordered_data.emp_row_count, 0) 
    ) 
     over (order by ordered_data.EmpName, ordered_data.TranDate) as page_number 
from  
    ordered_data  
order by 
    ordered_data.EmpName, ordered_data.TranDate 

Et, enfin ...

Inconvénients de cette solution:

  1. la page Hardcoded nombre de lignes.
  2. Nécessite une préparation de données spécifique dans la requête pour utiliser correctement la fonction d'agrégat.

Avantages de cette solution:

  1. fonctionne exactement :)

Mise à jour: amélioré pour gérer les blocs surdimensionnés, par exemple modifié.

+0

Je n'ai pas vraiment fait de fonctions d'agrégat définies par l'utilisateur auparavant. Je suis autorisé à avoir des procédures bien. – keiko

0

Que diriez-vous celui-ci: (100 est la limite de ligne par page)

select 
    a.*, floor(count(1) over (order by empno, empname)/100)+1 as page 
from source_table a 
order by page, empno, empname;