【问题标题】:(Oracle) How get total number of results when using a pagination query?(Oracle) 使用分页查询时如何获取结果总数?
【发布时间】:2010-05-25 13:46:58
【问题描述】:

我正在使用 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”进行“选择计数”,以获取 my_query 的结果总数(以便我可以将其展示给用户并使用它找出总页数等)。

有没有什么方法可以在不通过第二个查询(即从上面的查询中获取)的情况下获取结果总数?我尝试添加“max(rownum)”,但它似乎不起作用(我收到一个错误 [ORA-01747],这似乎表明它不喜欢我在 group by 中包含关键字 rownum)。

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

这正是我正在尝试做的事情,但我收到了 ORA-01747 错误,因为我认为它不喜欢我在 group by 中有 ROWNUM。请注意,如果有另一个解决方案不使用 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

【问题讨论】:

  • 前几天,我看到有人使用select a.*, count(*) over () from a ... 来获取每一行的总数。巧妙的技巧,但我不知道如何在这里应用它:(
  • 我不确定这个查询是如何工作的,但如果它是相对静态的,你能不能得到一次计数并将其缓存在应用程序变量中,然后只在查询更改时重新计数?我不知道是否有一种方法可以在不实际计算所有记录的情况下获取结果集的计数。
  • 当您有多个同样有效的建议时,您会怎么做?您应该在堆栈溢出时将哪一个标记为答案?
  • 我猜是你最终用来解决问题的那个。或者至少是与您的解决方案更相似的那个。

标签: sql oracle oracle10g


【解决方案1】:

我认为您必须将查询修改为类似这样才能在“单个”查询中获得所需的所有信息。

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 &lt;= 30 条件的记录数。

如果你不能接受这个查询的性能,或者执行 2 个单独的查询,也许你应该考虑一个解决方案,就像 FrustratedWithFormsDesigner 在他/她关于缓存计数的评论中提出的那样记录。

如果您经常使用数据库,我建议您获取一份SQL Cookbook 的副本。这是一本非常棒的书,里面有很多有用的技巧。

【讨论】:

  • 这与我在编辑部分下发布的 SQL 不同吗?
  • 不同之处在于您在第一个内部查询中有一个 where 子句(具有窗口函数来计算总数的那个)。 where 子句在窗口函数之前计算,因此查询实际上是计算行数小于或等于 30 的记录数。
  • 谢谢;我已经将我的实际 SQL 转换为堆栈溢出并将 count(*) over() 放在错误的位置。我已经用正确的 SQL 更新了 OP。
  • 注意:分析函数在可能的 DISTINCT 或 UNIQUE 关键字之前进行评估。如果最里面的 SELECT 有一个 DISTINCT 关键字,则 count(*) over () 必须在第二个 SELECT 中。否则它将计算所有行(包括重复行),但只会返回唯一的行(因此您会得到 5 行,但 RESULT_COUNT 将是例如 9)。如果没有 DISTINCT,count 函数也可以放在最里面的 SELECT 中。没有检查它对执行时间等有什么影响......
【解决方案2】:

不,如果不运行两次查询,或者运行一次并获取并缓存所有行以在开始显示它们之前对它们进行计数,您就无法做到这一点。两者都不可取,尤其是当您的查询开销很大或可能返回大量行时。

Oracle 自己的 Application Express (Apex) 工具提供了多种分页选项:

  1. 最有效的只是指示是否有“更多”行。为此,它仅获取比当前页面最大值多一行(例如,显示第 16-30 行的页面的 31 行)。
  2. 或者您可以显示有限的计数,可能会显示“16-30 of 67”或“16-30 of 200”。这意味着最多可获取 201 行(在此示例中)。这不如选项 1 有效,但比选项 3 更有效。
  3. 或者,您实际上可以显示“16-30 of 13,945”。为此,Apex 必须获取所有 13,945,但丢弃除第 15-30 行之外的所有数据。这是最慢、效率最低的方法。

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

【讨论】:

  • 嗯,在高层次上,您的答案是正确的,并且在 11 年前回答时可能在技术上是正确的......但是,APEX 现在所做的是计数 how许多 行条件将返回(而不是所有列数据 为所有条件选择的行),然后运行另一个查询(宾果游戏!)以实际返回行X 中的数据到 Z 的 Y。
【解决方案3】:

只是一个建议:

您可以考虑 Google“大约 13,000,000 个结果中的 1-10 个”方法 - 运行 COUNT(*) 作为原始查询的快速示例。我在这里假设给定的ABC 最多有一个XYZ

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 

很明显,样本会非常不准确和多变,所以这取决于要求是否合适。

【讨论】:

  • 另一个非常好的建议...这可能适用于我们的一些具有 50,000 个结果的报告(如果我们可以让客户批准更改要求)。
【解决方案4】:

这行得通吗?

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;

【讨论】:

  • 谢谢,但即使它确实有效,也需要运行“my_query”两次,这是我试图避免这样做的原因。
  • @BestPractices:你能在my_query的SELECT中包含count(*) over () total吗?
  • 更新了原始帖子以详细回答您的问题(它有效,但效果不佳)
【解决方案5】:

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

【讨论】:

    【解决方案6】:
    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 行。

    【讨论】:

      【解决方案7】:

      以 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 结果的原始代码的小错误修复。

      【讨论】:

        【解决方案8】:

        有没有什么方法可以在不通过第二个查询(即从上面的查询中获取)的情况下获取结果总数?

        对于 Oracle 12c,这可行:

        -- use count analytic function
        select a.*, count(*) over() as total_nb_results from mytable a
        offset 10 rows fetch next 10 rows only 
        

        它将从第 10 条记录开始,每批返回 10 行,以及请求返回的记录总数(如果没有“offset/fetch”子句)。

        另外,请注意,您也可以完美地使用 Oracle 10g 中的解析 count 函数。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-07
          • 1970-01-01
          • 2021-01-29
          • 2015-04-12
          相关资源
          最近更新 更多