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

发布于 2024-09-02 11:19:50 字数 2120 浏览 6 评论 0原文

我使用 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],似乎表明它不喜欢我在分组依据中使用关键字 rownum)。

我想要从原始查询中获取此信息而不是在单独的 SQL 语句中执行此操作的理由是“my_query”是一个昂贵的查询,因此我宁愿不要运行它两次(一次是为了获取计数,一次是为了获取数据页)如果我不需要的话;但是,无论我能想出什么解决方案来获取单个查询中的结果数量(同时获取我需要的数据页),如果可能的话,如果有任何额外的开销,都不会增加太多。请指教。

这正是我正在尝试做的事情,但我收到了 ORA-01747 错误,因为我相信它不喜欢我在分组依据中包含 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

I am using Oracle 10g and the following paradigm to get a page of 15 results as a time (so that when the user is looking at page 2 of a search result, they see records 16-30).

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

Right now I'm having to run a separate SQL statement to do a "select count" on "my_query" in order to get the total number of results for my_query (so that I can show it to the user and use it to figure out total number of pages, etc).

Is there any way to get the total number of results without doing this via a second query, i.e. by getting it from above query? I've tried adding "max(rownum)", but it doesn't seem to work (I get an error [ORA-01747] that seems to indicate it doesnt like me having the keyword rownum in the group by).

My rationale for wanting to get this from the original query rather than doing it in a separate SQL statement is that "my_query" is an expensive query so I'd rather not run it twice (once to get the count, and once to get the page of data) if I dont have to; but whatever solution I can come up with to get the number of results from within a single query (and at the same time get the page of data I need) should not add much if any additional overhead, if possible. Please advise.

Here is exactly what I'm trying to do for which I receive an ORA-01747 error because I believe it doesnt like me having ROWNUM in the group by. Note, If there is another solution that doesnt use max(ROWNUM), but something else, that is perfectly fine too. This solution was my first thought as to what might work.

 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

--------- EDIT --------
Note, based on the first comment I tried the following that appears to work. I dont know how well it performs versus other solutions though (I'm looking for the solution that fufills my requirement but performs the best). For example, when I run this it takes 16 seconds. When I take out the COUNT(*) OVER () RESULT_COUNT it takes just 7 seconds:

    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

The explain plan changes from doing a SORT (ORDER BY STOP KEY) to do a WINDOW (SORT).

Before:

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

After:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

只为守护你 2024-09-09 11:19:50

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

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 条件。

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

如果您定期使用数据库,我建议您获取一份 SQL Cookbook。这是一本出色的书,其中包含许多有用的技巧。

I think you have to modify your query to something like this to get all the information you want on a "single" query.

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 

The reason is that the COUNT(*) OVER() window function gets evaluated after the WHERE clause, hence not giving the total count of records but the count of records that satisfy the ROWNUM <= 30 condition.

If you cannot accept the performance ot this query, or of executing 2 separate queries, maybe you should think about a solution like the one proposed by FrustratedWithFormsDesigner in his/her comment about caching the count of records.

If you work with databases on a regular basis I recommend you get a copy of SQL Cookbook. It is an exceptional book with lots of useful tips.

oО清风挽发oО 2024-09-09 11:19:50

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

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

  1. 最有效的只是指示是否有“更多”行。为此,它仅比当前页面最大值多获取一行(例如,显示第 16-30 行的页面为 31 行)。
  2. 或者您可以显示有限的计数,可能显示“67 中的 16-30”或“超过 200 中的 16-30”。这意味着最多可获取 201 行(在本示例中)。这不如选项 1 有效,但比选项 3 更有效。
  3. 或者您确实可以显示“13,945 中的 16-30”。为此,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);

No, you can't do it without either running the query twice, or running it once and fetching and caching all the rows to count them before starting to display them. Neither is desirable, especially if your query is expensive or potentially returns a lot of rows.

Oracle's own Application Express (Apex) tool offers a choice of pagination options:

  1. The most efficient just indicates whether or not there are "more" rows. To do this it fetches just one more row than the current page maximum (e.g. 31 rows for page showing rows 16-30).
  2. Or you can show a limited count that may show "16-30 of 67" or "16-30 of more than 200". This means is fetches up to 201 (in this example) rows. This is not as efficient as option 1, but more efficient than option 3.
  3. Or you can, indeed, show "16-30 of 13,945". To do this Apex has to fetch all 13,945 but discard all but rows 15-30. This is the slowest, least efficient method.

The pseudo-PL/SQL for option 3 (your preference) would be:

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);
奢望 2024-09-09 11:19:50

只是一个建议:

您可以考虑 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 

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

Just a suggestion:

You could consider the Google "1-10 of approximately 13,000,000 results" approach - run the COUNT(*) as a quick sample over the original query. I've assumed here that there is at most one XYZ for a given 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 

Obviously, the sample will be quite inaccurate and variable, so it depends on the requirements whether this is appropriate or not.

辞别 2024-09-09 11:19:50

这有效吗?

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;

Does this work?

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;
违心° 2024-09-09 11:19:50

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

Another solution would be to create a materialized view that maintains counts for each value of ABC.XYZ_ID - that way you push the burden of getting the count to processes that insert/update/delete rows in the table.

夜空下最亮的亮点 2024-09-09 11:19:50
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 行。

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

This query will calculate how many groups of pages you will need to fetch, and fetch the data as one query.

From the result set, process 15 rows at a time. The very last set of rows, may be shorter than 15.

筱果果 2024-09-09 11:19:50

以 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 结果。

To build on EvilTeach's answer:

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

where XX is the page you want.

The above solution includes a small bugfix to the original code that caused the first page to return PAGE_SIZE - 1 results.

从来不烧饼 2024-09-09 11:19:50

是否有任何方法可以获取结果总数,而无需通过第二个查询执行此操作,即从上面的查询中获取结果?

对于 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 函数。

Is there any way to get the total number of results without doing this via a second query, i.e. by getting it from above query?

For Oracle 12c, this would work:

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

It will return batches of 10 rows each, starting at the 10th record, alongside the total number of records returned by the request (if there where no "offset/fetch" clause).

Also, note that you can perfectly use the analytic count function in Oracle 10g too.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文