使用 Oracle 进行分页
Posted
技术标签:
【中文标题】使用 Oracle 进行分页【英文标题】:Paging with Oracle 【发布时间】:2010-09-19 11:44:40 【问题描述】:我对 Oracle 的熟悉程度不如我想的那样。我有一些 250k 记录,我想每页显示 100 条记录。目前,我有一个存储过程,它使用数据适配器、数据集和存储过程结果的 dataadapter.Fill(dataset) 方法将一百万条记录的所有四分之一检索到数据集。如果我将“页码”和“每页的记录数”作为整数值,我可以作为参数传递,那么返回该特定部分的最佳方法是什么。比如说,如果我将 10 作为页码传递,将 120 作为页数传递,那么从 select 语句中它会给我第 1880 到第 1200,或者类似的东西,我脑海中的数学可能会出错。
我在 .NET 中使用 C# 执行此操作,我认为这并不重要,如果我能在 sql 端正确处理它,那么我应该很酷。
更新:我能够使用 Brian 的建议,并且效果很好。我想进行一些优化,但页面会在 4 到 5 秒而不是一分钟内出现,而且我的分页控件能够很好地与我的新存储过程集成。
【问题讨论】:
【参考方案1】:这样的东西应该可以工作:From Frans Bouma's Blog
SELECT * FROM
(
SELECT a.*, rownum r__
FROM
(
SELECT * FROM ORDERS WHERE CustomerID LIKE 'A%'
ORDER BY OrderDate DESC, ShippingDate DESC
) a
WHERE rownum < ((pageNumber * pageSize) + 1 )
)
WHERE r__ >= (((pageNumber-1) * pageSize) + 1)
【讨论】:
是的,它是 Oracle 支持的“内置”列,它始终从 1 开始,每行递增。因此,在这段 sn-p 代码中,如果您有 1000 行,则应用排序顺序,然后为每一行分配一个 rownum。外部选择使用这些行号根据您的页面大小来定位您正在寻找的“页面”。 这很好,但在大选择时速度非常慢,只需检查选择 0 到 1000 和 500.000 到 501.000 的时间...我现在正在使用这种选择结构寻找解决方法。 @n3whous3 你可以试试这个 - inf.unideb.hu/~gabora/pagination/results.html 我想知道为什么两个WHERE
不能和AND
结合起来,然后发现:orafaq.com/wiki/ROWNUM
Oracle 分页毁了我的一天。【参考方案2】:
Ask Tom 谈分页和非常非常有用的分析功能。
这是该页面的摘录:
select * from (
select /*+ first_rows(25) */
object_id,object_name,
row_number() over
(order by object_id) rn
from all_objects
)
where rn between :n and :m
order by rn;
【讨论】:
这实际上是一个更好的实现,尽管在那个帖子上很难找到。当您有很多大页面时,另一个答案也必须遍历前一页的所有行。在复杂的查询中,这意味着后面的页面比前面的页面执行得更差。 @tallseth 你是对的。在那个页面上很难找到它。添加了摘录。 如果您想动态更改订单,这是正确的答案。 嗨,如果在这种情况下我使用“rownum rn”而不是“row_number(...) rn”会发生什么?【参考方案3】:为了完整起见,对于寻求更现代解决方案的人们,Oracle 12c 中有一些新功能,包括更好的分页和顶部处理。
分页
分页看起来像这样:
SELECT *
FROM user
ORDER BY first_name
OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY;
前 N 条记录
获取最高记录如下所示:
SELECT *
FROM user
ORDER BY first_name
FETCH FIRST 5 ROWS ONLY
请注意上述两个查询示例如何具有ORDER BY
子句。新命令尊重这些并在排序后的数据上运行。
我找不到关于 FETCH
或 OFFSET
的良好 Oracle 参考页面,但 this page 对这些新功能有很好的概述。
性能
正如@wweicker 在下面的 cmets 中指出的那样,性能是 12c 中新语法的一个问题。我没有 18c 的副本来测试 Oracle 是否对其进行了改进。
有趣的是,当我第一次对我的表(1.13 亿多行)运行新方法的查询时,我的实际结果返回得稍微快了一点:
新方法:0.013 秒。 旧方法:0.107 秒。然而,正如@wweicker 所提到的,新方法的解释计划看起来更糟糕:
新方法成本:300,110 旧方法成本:30新语法导致对我的列上的索引进行全面扫描,这就是全部成本。当限制未索引的数据时,情况可能会变得更糟。
让我们看看在前一个数据集上包含单个未索引列时:
新方法时间/成本:189.55 秒/998,908 旧方法时间/成本:1.973 秒/256总结:在 Oracle 改进此处理之前谨慎使用。如果你有一个索引可以使用,也许你可以使用新方法。
希望我很快就有 18c 的副本可以玩并且可以更新
【讨论】:
这是 12c 用户的最佳答案 语法更简洁,但性能更差 (dba-presents.com/index.php/databases/oracle/…) 很高兴知道,谢谢@wweicker。希望 Oracle 尽快修复性能;虽然,知道 Oracle,这可能是一个遥远的希望! 语法是新的,它被转换为常规的 ROW_NUMBER/RANK 调用。相关How do I limit the number of rows returned by an Oracle query after ordering? 看起来性能问题已由 Oracle 处理。看这里 - blogs.oracle.com/optimizer/fetch-first-rows-just-got-faster【参考方案4】:只想总结一下答案和cmets。分页有多种方法。
在 oracle 12c 之前没有 OFFSET/FETCH 功能,因此请按照 @jasonk 的建议查看 whitepaper。这是我找到的关于不同方法的最完整的文章,并详细解释了优缺点。在这里复制粘贴需要很长时间,所以我不会这样做。
还有一篇来自 jooq 创建者的好文章,解释了 Oracle 和其他数据库分页的一些常见警告。 jooq's blogpost
好消息,从 oracle 12c 开始,我们有了新的 OFFSET/FETCH 功能。 OracleMagazine 12c new features。请参考《Top-N 查询与分页》
您可以通过发出以下语句来检查您的 oracle 版本
SELECT * FROM V$VERSION
【讨论】:
【参考方案5】:尝试以下方法:
SELECT *
FROM
(SELECT FIELDA,
FIELDB,
FIELDC,
ROW_NUMBER() OVER (ORDER BY FIELDC) R
FROM TABLE_NAME
WHERE FIELDA = 10
)
WHERE R >= 10
AND R <= 15;
通过 [tecnicume]
【讨论】:
【参考方案6】:在我的项目中,我使用了 Oracle 12c 和 java。分页代码如下所示:
public public List<Map<String, Object>> getAllProductOfferWithPagination(int pageNo, int pageElementSize, Long productOfferId, String productOfferName)
try
if(pageNo==1)
//do nothing
else
pageNo=(pageNo-1)*pageElementSize+1;
System.out.println("algo pageNo: " + pageNo +" pageElementSize: "+ pageElementSize+" productOfferId: "+ productOfferId+" productOfferName: "+ productOfferName);
String sql = "SELECT * FROM ( SELECT * FROM product_offer po WHERE po.deleted=0 AND (po.product_offer_id=? OR po.product_offer_name LIKE ? )" +
" ORDER BY po.PRODUCT_OFFER_ID asc) foo OFFSET ? ROWS FETCH NEXT ? ROWS ONLY ";
return jdbcTemplate.queryForList(sql,new Object[] productOfferId,"%"+productOfferName+"%",pageNo-1, pageElementSize);
catch (Exception e)
System.out.println(e);
e.printStackTrace();
return null;
【讨论】:
以上是关于使用 Oracle 进行分页的主要内容,如果未能解决你的问题,请参考以下文章