执行查询以获得符合某些条件的所有记录的总数,然后对其进行分页?

问题描述 投票:0回答:1

我需要查询分页数据。要求之一是不仅要显示给定页面上的项目,还要显示用户可以查看的项目总数(支持页面导航,以及向用户发出通知,例如“显示 159 个项目中的第 50-75 个项目”) ”)。我在我正在处理的代码库中看到了一种我不太喜欢的模式;它基本上相当于将所有数据插入到临时表中,然后选择页面的特定数据,然后选择计数。看起来像这样。

CREATE PROCEDURE [dbo].[GetAllFoos] (   --Some parameters to filter the foos
                                        @PageIndex   INT = 1,
                                        @PageSize    INT = 25,
                                        @TotalCount  INT OUTPUT )
AS
BEGIN
    SELECT --Some large number of fields
    INTO   #Foos
    FROM   [dbo].Foo
    WHERE  --Some condition is true

    SELECT @TotalCount = Count(1)
    FROM   #Foos;

    SELECT   *
    FROM     #Foos
    ORDER BY FooID 
    OFFSET ((@PageIndex - 1) * @PageSize) ROWS
    FETCH NEXT @PageSize ROWS ONLY
END

上面查询了整个 foo 集合,并将其中的所有数据获取到临时表中。这可能是数百或数千条记录,然后(默认情况下)它会丢弃除其中 25 条之外的所有记录。这是计算的巨大浪费。就我而言,我通过查看 SSMS 中的客户端统计信息找到了一种稍微更好的方法

CREATE PROCEDURE [dbo].[GetAllFoos] (   --Some other parameters to filter the foos
                                        @PageIndex   INT = 1,
                                        @PageSize    INT = 25,
                                        @TotalCount  INT OUTPUT )
AS
BEGIN
    SELECT --Some large number of fields
    FROM   [dbo].Foo
    WHERE  --Some condition is true
    ORDER BY FooID 
    OFFSET ((@PageIndex - 1) * @PageSize) ROWS
    FETCH NEXT @PageSize ROWS ONLY

    SELECT @TotalCount = Count(1) 
    FROM [dbo].Foo
    WHERE  --Some condition is true
END

修改后的查询的第一部分,仅选择我需要的所有数据行本身花费的时间较少,但我遇到了一个问题,添加辅助查询会立即重新添加该时间。不过,我能够缓解这个问题通过向

[dbo].Foo
添加一个围绕我想要查询的条件定制的索引,它的执行速度大约提高了 4 倍(同样,根据 ssms 中的客户端统计数据)。

也就是说,我觉得所有这些都相当笨重——我知道 sql 的级别非常低,但是有没有更好的方法来实现这个目标?我可以进行 where 子句过滤,获取计数,然后在下一步中在单个操作中执行

offset ... rows / fetch next ... rows
子句?无论我尝试什么,查询计划似乎总是显示两个查询路径,有时针对不同的索引,但总是两个单独的读取操作。下面是运行第二个查询的示例(真实的表/索引名称已模糊)。

sql t-sql pagination query-optimization
1个回答
0
投票

如果不实际读取所有行,就无法“获取总数”。

你能得到的最好的结果就是使用窗口函数:

SELECT
  f.*
FROM (
    SELECT *,
      TotalCount = Count(*),
      RowNumber = ROW_NUMBER() OVER (ORDER BY FooID)
    FROM [dbo].Foo
    WHERE  --Some condition is true
) f
WHERE f.RowNumber >= (PageIndex - 1) * PageSize + 1
  AND f.RowNumber < PageIndex * PageSize;

根据选择,我可能会放弃获取总数。在绝大多数情况下实际上没有必要。

说实话,你最好使用 Keyset Pagination,它更高效。您只需跳转到一个 ID(或从头开始)并从那里开始读取。实际上不会读取未返回的行。 请参阅在 SQL Server 中是否有更好的选项来应用分页而不应用 OFFSET?

但这需要对整个堆栈一直到 UI 进行彻底的重新思考。您不会获得总行数,也不会按行号进行分页。因此,您无法向用户显示页数,甚至无法向用户显示它们所在的页码。它就像一个无限滚动的页面,只有上一个和下一个按钮。

© www.soinside.com 2019 - 2024. All rights reserved.