(Oracle) 使用分页查询时如何获取结果总数?
我使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
我认为您必须将查询修改为类似的内容,才能在“单个”查询中获取您想要的所有信息。
原因是
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.
The reason is that the
COUNT(*) OVER()
window function gets evaluated after theWHERE
clause, hence not giving the total count of records but the count of records that satisfy theROWNUM <= 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.
不,如果不运行查询两次,或者运行一次查询并获取并缓存所有行以在开始显示它们之前对其进行计数,则无法做到这一点。两者都不是可取的,特别是当您的查询成本昂贵或可能返回大量行时。
Oracle 自己的 Application Express (Apex) 工具提供了多种分页选项:
选项 3(您的偏好)的伪 PL/SQL 为:
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:
The pseudo-PL/SQL for option 3 (your preference) would be:
只是一个建议:
您可以考虑 Google“大约 13,000,000 个结果中的 1-10”方法 - 在原始查询上运行 COUNT(*) 作为快速示例。我在这里假设对于给定的 ABC 至多有一个 XYZ :
显然,样本将非常不准确且可变,因此这取决于要求是否是否合适。
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 givenABC
:Obviously, the sample will be quite inaccurate and variable, so it depends on the requirements whether this is appropriate or not.
这有效吗?
Does this work?
另一个解决方案是创建一个物化视图来维护 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.此查询将计算您需要获取多少组页面,并将数据作为一个查询获取。
从结果集中,一次处理 15 行。最后一组行可能少于 15 行。
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.
以 EvilTeach 的答案为基础:
其中 XX 是您想要的页面。
上述解决方案包括对原始代码的一个小错误修复,该错误修复导致第一页返回 PAGE_SIZE - 1 结果。
To build on EvilTeach's answer:
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.
对于 Oracle 12c,这可行:
它将返回每批 10 行,从第 10 条记录开始,以及请求返回的记录总数(如果没有“offset/fetch”子句)。
另请注意,您也可以完美地使用 Oracle 10g 中的分析
count
函数。For Oracle 12c, this would work:
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.