Sql (Oracle)如何为分页对行进行分组

Sql (Oracle)如何为分页对行进行分组,sql,oracle,oracle10g,pagination,Sql,Oracle,Oracle10g,Pagination,我有一个表,其输出与此类似(尽管以千为单位): 我期望的输出与此类似: EMPNO ENAME TRANDATE AMT PAGE ---------- ---------- --------- ------- ---- 100 Alison 21-MAR-96 45000 1 100 Alison 12-DEC-78 23000 1 100 Alison 24-OCT-82

我有一个表,其输出与此类似(尽管以千为单位):

我期望的输出与此类似:

     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

基本上,它应该插入一个新字段来标识它所属的页面。分页符基于行。而且,就像EMPNO中的“保持在一起”一样,当行无法添加下一个EMPNO批时,它会向页面添加1。这是Excel的限制,因为Excel不允许单个工作表中的行数超过65000行。在示例中,只有4行。限制号是静态的。

以下SQL语句将我的EMP表中的20条记录拆分为五页,每页四行:

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>

在普通SQL中做这样的事情太难了,甚至是不可能的

但有一些局限性,这个问题可以借助于

首先,使用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

);
创建类型主体:

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;
创建要与类型一起使用的AGREATION函数:

create function page_num_agg (
  input number
) return number aggregate using page_num_agg_type;
接下来准备数据并使用新函数计算页码:

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
最后

此解决方案的缺点

  • 硬编码页行数
  • 在查询中需要一些特定的数据准备,才能正确使用聚合函数
  • 此解决方案的优点

  • 只是工作:)


  • 更新:改进以处理超大块,示例已修改。

    ThinkJet认为其他一些答案不符合“保持在一起”的要求,这是正确的。但是,我认为这可以在不使用用户定义的聚合的情况下完成

    样本数据

    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);
    
    现在,确定每个empno段的结束行(使用RANK查找起始行,使用COUNT..PARTITION BY查找段中的数字)

    然后使用APC解决方案中的ceil/4将它们分组到“页面”中。同样,正如ThinkJet所指出的,规范中存在一个问题,因为它不能满足empno“保持在一起”段中的记录超过一页所能容纳的情况

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

    正如ThinkJet所指出的,这种解决方案不是防弹的

    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
    ;
    
    升降台试验吹扫;
    创建表测试(empno编号、ename varchar2(20)、传输日期、金额编号);
    声明
    游标csr\u名称为
    选择rownum emp_id,
    解码(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
    从dual connect by level看,这个怎么样:(100是每页的行限制)


    这是行不通的。重要条件-empno不能分为两组。在您的情况下,EMPNO101将分为两组。谢谢!这个代码适合我!(更新:好的……也许不是。迈克尔,这是个好发现。)只是不起作用——结果不对。例如,第3页及以后的页面末尾的其他空行在哪里计数?那么,您能否保证没有一个EMPNO的记录超过65000条?另外,您使用的是哪个版本的Excel?Excel 2007允许每个工作表有1048576行,我希望它能支持2007年以前的Excel版本。()在我目前的情况下(我认为),一个EMPNO很可能不会有超过65k条记录。加里的解决方案解决了我的问题。非常感谢你们所有人!我以前没有真正做过任何用户定义的聚合函数。不过我可以做手术。非常感谢!这对我的案子有效!我真的认为这个问题会更长。我了解65k+记录使用相同EMPNO的问题,但在我的情况下,这很可能不会发生。与其他答案中的错误相同:计算中未处理页面末尾的随机间隙。例如,假设您有3组行(A、B、C),每组6行。尝试将其放在页面上,每页6行:组A-rank()=1-Ok,第1页;B组-rank()=7-确定,第2页从顶部填充,第1页保留4行空白;C组-秩()=13-错误!,似乎适合于第2页,但必须放在第3页,因为REAL rank()值必须包含第1页的空行:6+4+6+1=17。在构建真正的页面之前,无法预测空行的数量。即使是65k+行,甚至在第二页末尾,也可能发生这种情况:例如,第一页上的5个空闲行+2页上的4个空闲行+下一个块的大小为5到9行……啊,我明白了ThinkJet的问题。第1页末尾有5行的空间,但下一组是6行。这六行被正确地推到第2页,但这六行没有被正确地计算为第2页的一部分。所以,当计算第二页可以容纳多少人时,可能会出错。我想我有点理解这个问题。我只是太兴奋了,很快就检查了我的测试(用一个50k的块正确地输出了一个两页的文件)。但是当我用较小的块大小(300)测试查询时,我确实注意到了块的减少。
    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
    ;
    
    select
        a.*, floor(count(1) over (order by empno, empname)/100)+1 as page
    from source_table a
    order by page, empno, empname;