2010-08-09 14 views
6

Tengo una tabla que da salida similar a esto (aunque en miles):(Oracle) la forma de agrupar filas para paginación

 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 

Mi salida deseada sería similar a esto:

 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 

Básicamente, debe insertar un nuevo campo para identificar la página a la que pertenece. El salto de página se basa en las filas. Y, como si "se mantuvieran juntas" en EMPNO, agrega 1 a PAGE cuando las filas no pueden agregar el siguiente lote de EMPNO. Es para el límite de Excel, ya que Excel no permite más de 65000 filas (más o menos) en una sola hoja. En el caso de la muestra, son solo 4 filas. El número límite es estático.

+0

tanto, se puede garantizar que no tendrá EMPNO> 65000 registros? – APC

+0

Además, ¿qué versión de Excel estás usando? Excel 2007 permite 1.048.576 filas insanas por hoja de trabajo. – APC

+0

Bueno, esperaba que fuera compatible incluso con las versiones de Excel anteriores a 2007. (http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP005199291.aspx) – keiko

Respuesta

2

ThinkJet tiene razón en que algunas de las otras respuestas no satisfacen el requisito de 'mantener juntas'. Sin embargo, creo que esto se puede hacer sin recurrir a un agregado definido por el usuario.

datos de muestra

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); 

Ahora, determinan la fila final de cada segmento empno (usando RANK para encontrar el principio y COUNT..PARTITION POR para encontrar el número en el segmento).

Luego use ceil/4 de la solución de APC para agruparlos en sus 'páginas'. Una vez más, como señala ThinkJet, hay un problema en la especificación, ya que no satisface la situación cuando hay más registros en el segmento de "mantener juntos" que los que pueden caber en una página.

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

Como ha señalado ThinkJet, esta solución no es a prueba de balas.

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 
; 

Así, con tamaño de fragmento de 25 y el tamaño del grupo de 11, obtenemos los saltos donde encaja a 33 personas en el trozo a pesar del límite de 25. Los tamaños grandes de trozos y los grupos pequeños deberían hacer que esto sea poco frecuente, pero desearía dejar algo de margen. Así que tal vez establezca los trozos en 65,000 en lugar de ir hasta 65,536.

+0

¡Muchas gracias! Esto funciona para mi caso! Y realmente pensé que la consulta sería más larga. Comprendo el problema de los 65k + registros con el mismo EMPNO, pero en mi caso, probablemente no esté sucediendo. – keiko

+1

Hay el mismo error que en otras respuestas: huecos aleatorios al final de la página no manejados en el cálculo. P.ej. supongamos que tiene 3 grupos de filas (A, B, C) con 6 filas en cada grupo. Intente colocarlo en las páginas, 6 filas por página: Grupo A - rango() = 1 - Bien, 1ra página; Grupo B - rango() = 7 - Bien, la 2ª página se llena desde la parte superior y 4 filas se dejan en blanco en la 1ª página; Grupo C - rank() = 13 - ERR !, parece encajar en la 2ª página pero debe colocarse en 3º, porque el valor REAL rank() debe incluir filas vacías desde la 1ª página: 6 + 4 + 6 + 1 = 17. No hay manera de predecir el número de filas vacías antes de la construcción de la página real. – ThinkJet

+0

Esto puede suceder incluso para más de 65k filas, incluso al final de la segunda página: P. ej. 5 filas libres en la 1ª página + 4 filas libres en la 2ª página + bloque siguiente tienen un tamaño de 5 a 9 filas ... – ThinkJet

1

La siguiente instrucción SQL divide los veinte discos en mi tabla EMP en cinco páginas de cuatro filas cada uno:

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

no funcionará. Condición importante: ese empno no puede estar en dos grupos. En su caso, empno 101 estará en dos grupos separados. –

+0

¡Gracias! ¡Este código funciona para mí! (Actualización: Ok ... tal vez no. Buen descubrimiento, Michael.) – keiko

+0

Simplemente no funciona - resultados incorrectos. P.ej. ¿Dónde se cuentan las líneas vacías adicionales al final de la página para la 3ª página y más adelante? – ThinkJet

2

Es demasiado complicado o incluso imposible de hacer tal cosa en SQL sin formato.

Pero con algunas limitaciones el problema se puede resolver con la ayuda de user-defined aggregate functions.

En primer lugar, crear el objeto con ODCIAggregate implementación de la interfaz:

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 

); 

Crear tipo de cuerpo:

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; 

función Crear agrreation a utilizar con el tipo:

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

Siguiente preparar los datos y use una nueva función para calcular los números de página:

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 

Y, finalmente ...

Desventajas de esta solución:

  1. Hardcoded recuento de páginas fila.
  2. Requiere cierta preparación de datos específicos en la consulta para usar la función de agregado correctamente.

Ventajas de esta solución:

  1. Sólo funciona :)

Actualizado: mejorado para manejar bloques de gran tamaño, ejemplo modificado.

+0

No he hecho ninguna función agregada definida por el usuario anteriormente. Sin embargo, me permiten tener procedimientos. – keiko

0

¿Qué tal este: (100 es el límite de filas por página)

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