(Oracle)使用分页查询时如何获得结果总数?

时间:2010-05-25 13:46:59

标签: sql oracle oracle10g

我正在使用Oracle 10g和以下范例来获取15个结果的页面(因此当用户查看搜索结果的第2页时,他们会看到记录16-30)。

select * 
  from 
( select rownum rnum, a.*
    from (my_query) a
   where rownum <= 30 )
where rnum > 15;

现在我必须运行一个单独的SQL语句来对“my_query”执行“select count”以获取my_query的结果总数(以便我可以向用户显示并使用它计算总页数等)。

有没有办法在不通过第二个查询执行此操作的情况下获取结果总数,即从上面的查询中获取结果?我已经尝试添加“max(rownum)”,但它似乎不起作用(我得到一个错误[ORA-01747]似乎表明它不喜欢我在组中有关键字rownum)。

我希望从原始查询中获取此内容而不是在单独的SQL语句中执行此操作的理由是“my_query”是一个昂贵的查询,所以我宁愿不运行它两次(一次得到计数,一次如果我不需要,那么获取数据页面;但是,如果可能的话,我可以在单个查询中获得结果数量(同时获取我需要的数据页面)的任何解决方案都不应该增加很多额外的开销。请指教。

这正是我正在尝试做的事情,因为我认为它不喜欢我在组中使用ROWNUM,因此我收到了ORA-01747错误。注意,如果有另一种解决方案不使用max(ROWNUM),而是使用其他方法,那也完全没问题。这个解决方案是我第一次想到什么可行。

 SELECT * FROM (SELECT r.*, ROWNUM RNUM, max(ROWNUM)
 FROM (SELECT t0.ABC_SEQ_ID AS c0, t0.FIRST_NAME, t0.LAST_NAME, t1.SCORE
 FROM ABC t0, XYZ t1
 WHERE (t0.XYZ_ID = 751) AND 
 t0.XYZ_ID = t1.XYZ_ID 
 ORDER BY t0.RANK ASC) r WHERE ROWNUM <= 30 GROUP BY r.*, ROWNUM) WHERE RNUM > 15

---------编辑-------- 请注意,基于第一个评论我尝试了以下似乎工作。我不知道它与其他解决方案相比表现如何(我正在寻找满足我的要求但表现最佳的解决方案)。例如,当我运行它需要16秒。当我取出COUNT(*)OVER()RESULT_COUNT时,只需7秒钟:

    SELECT * FROM (SELECT r.*, ROWNUM RNUM, ) 
    FROM (SELECT COUNT(*) OVER () RESULT_COUNT, 
          t0.ABC_SEQ_ID AS c0, t0.FIRST_NAME, t1.SCORE 
    FROM ABC t0, XYZ t1 
    WHERE (t0.XYZ_ID = 751) AND t0.XYZ_ID = t1.XYZ_ID 
    ORDER BY t0.RANK ASC) r WHERE ROWNUM <= 30) WHERE RNUM > 1

解释计划从执行SORT(ORDER BY STOP KEY)变为WINDOW(SORT)。

在:

SELECT STATEMENT () 
 COUNT (STOPKEY)    
  VIEW ()   
   SORT (ORDER BY STOPKEY)  
    NESTED LOOPS () 
     TABLE ACCESS (BY INDEX ROWID)  XYZ
      INDEX (UNIQUE SCAN)   XYZ_ID
     TABLE ACCESS (FULL)    ABC

后:

SELECT STATEMENT () 
 COUNT (STOPKEY)    
  VIEW ()   
   WINDOW (SORT)    
    NESTED LOOPS () 
     TABLE ACCESS (BY INDEX ROWID)  XYZ
      INDEX (UNIQUE SCAN)   XYZ_ID
     TABLE ACCESS (FULL)    ABC

8 个答案:

答案 0 :(得分:20)

我认为您必须将查询修改为类似的内容,以便在“单个”查询中获取所需的所有信息。

SELECT *
FROM (SELECT r.*, ROWNUM RNUM, COUNT(*) OVER () RESULT_COUNT 
      FROM (SELECT t0.ABC_SEQ_ID AS c0, t0.FIRST_NAME, t1.SCORE
            FROM ABC t0, XYZ t1
            WHERE (t0.XYZ_ID = 751) 
            AND t0.XYZ_ID = t1.XYZ_ID 
            ORDER BY t0.RANK ASC) R)
WHERE RNUM between 1 and 15 

原因是COUNT(*) OVER()窗口函数在WHERE子句之后被评估,因此不会给出记录的总数,而是给出满足ROWNUM <= 30条件的记录的数量。 / p>

如果您无法接受此查询的性能,或执行2个单独的查询,您可能应该考虑像 FrustratedWithFormsDesigner 在他/她关于缓存计数的评论中提出的解决方案记录。

如果您定期使用数据库,我建议您获取SQL Cookbook的副本。这是一本特别的书,有很多有用的提示。

答案 1 :(得分:2)

只是一个建议:

你可以考虑谷歌“ 1-10大约13,000,000个结果”的方法 - 运行COUNT(*)作为原始查询的快速样本。我在此假设给定的XYZ最多只有一个ABC

SELECT *
FROM (SELECT r.*, ROWNUM RNUM, 
      (SELECT COUNT(*) * 100
       FROM ABC SAMPLE(1) t0
       WHERE (t0.XYZ_ID = 751)
      ) RESULT_COUNT 
  FROM (SELECT t0.ABC_SEQ_ID AS c0, t0.FIRST_NAME, t1.SCORE
        FROM ABC t0, XYZ t1
        WHERE (t0.XYZ_ID = 751) 
        AND t0.XYZ_ID = t1.XYZ_ID 
        ORDER BY t0.RANK ASC) R)
WHERE RNUM between 1 and 15 

显然,样本将是非常不准确和可变的,因此它取决于是否合适的要求。

答案 2 :(得分:1)

这有用吗?

select * 
  from 
( select rownum rnum, a.*, b.total
    from (my_query) a,   (select count(*) over () total from my_query) b
   where rownum <= 30 )
where rnum > 15;

答案 3 :(得分:1)

不,如果没有运行查询两次,或者运行一次并在开始显示它们之前获取并缓存所有行来计算它们,则无法执行此操作。这两者都不可取,特别是如果您的查询很昂贵或可能返回很多行。

Oracle自己的Application Express(Apex)工具提供了一系列分页选项:

  1. 效率最高只表示是否有“更多”行。为此,它只获取比当前页面最大值多一行(例如,显示行16-30的页面为31行)。
  2. 或者您可以显示有限数量,可能会显示“16-30 of 67”或“16-30 in 200”。这意味着最多可获取201(在此示例中)行。这不如选项1有效,但比选项3更有效。
  3. 或者你可以确实显示“13,945的16-30”。要做到这一点,Apex必须获取所有13,945但丢弃除15-30行之外的所有内容。这是最慢,效率最低的方法。
  4. 选项3的伪PL / SQL(您的偏好)将是:

    l_total := 15;
    for r in 
      ( select * 
          from 
        ( select rownum rnum, a.*
            from (my_query) a
        )
        where rnum > 15
      )
    loop
       l_total := l_total+1;
       if runum <= 30 then
          print_it;
       end if;
    end loop;
    show_page_info (15, 30, l_total);
    

答案 4 :(得分:1)

WITH
base AS
(
    SELECT ROWNUM RNUM, A.*
    FROM (SELECT * FROM some_table WHERE some_condition) A
)
SELECT FLOOR(((SELECT COUNT(*) FROM base) / 15) + 1) TOTAL_PAGES_TO_FETCH, 
       ((ROWNUM - MOD(ROWNUM, 15)) / 15) + 1 PAGE_TO_FETCH,
       B.*
FROM base B

此查询将计算您需要获取的页面组数,并将数据作为一个查询获取。

从结果集中,一次处理15行。最后一组行可能短于15。

答案 5 :(得分:1)

另一个解决方案是创建一个物化视图来维护ABC.XYZ_ID的每个值的计数 - 这样就可以减轻计数到表中插入/更新/删除行的进程的负担。

答案 6 :(得分:0)

以EvilTeach的答案为基础:

WITH
base AS
(
    SELECT (ROWNUM - 1) RNUM, A.*
    FROM (SELECT * FROM some_table WHERE some_condition) A
)
SELECT V.* FROM (
  SELECT FLOOR(((SELECT COUNT(*) FROM base) / 15) + 1) TOTAL_PAGES_TO_FETCH, 
         ((RNUM - MOD(RNUM, 15)) / 15) + 1 PAGE_TO_FETCH,
         B.*
  FROM base B
) V
WHERE V.PAGE_TO_FETCH = xx

其中XX是您想要的页面。

上述解决方案包含原始代码的小错误修正,导致第一页返回PAGE_SIZE - 1结果。

答案 7 :(得分:-1)

对于Oracle 12c,这将起作用:

select a.*, count(*)over() from mytable a offset 10 fetch first 10 rows only