使用 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 子句。新命令尊重这些并在排序后的数据上运行。

我找不到关于 FETCHOFFSET 的良好 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 进行分页的主要内容,如果未能解决你的问题,请参考以下文章

react-slick 自定义分页分页道具使用

单页分页问题中的多个角度材料表

一个视图中的 CI 多页分页,

当我在基于类的视图中应用过滤器时,如何在 django 中使用分页分页。网址总是不断变化我如何跟踪网址

梦内容页分页标题提取

dede织梦文章页分页导航副标题如何删除标题后面带的#号