【问题标题】:SQL Server Pagination with different line number per page每页具有不同行号的 SQL Server 分页
【发布时间】:2016-06-22 10:40:22
【问题描述】:

我在 SQL Server 数据库中有一个表,其中包含:

  • int 值(列名:Value
  • datetime 值(列名:Date
  • bit 值(列名:LastLineOfPage

我想对此表进行分页查询。分页的逻辑如下:

  • 在按Date 列对行进行排序后,查询必须返回与给定页面(参数@PageNumber)对应的行
  • 此外,查询必须给出所有先前页面行的SUM
  • 每页的行号不固定:默认为每页 14 行,但如果位 LastLineOfPage 为真,则页面仅包含行,直到具有真值的那一行

以下是该过程的综合视图:

这是文本中的数据:

ID  DATE      VALUE LASTLINEOFPAGE
1   07/10/2006  10  0
2   14/10/2006  12  0
3   21/10/2006  4   1
4   28/10/2006  6   0
5   04/11/2006  8   1
6   25/11/2006  125 0
7   02/12/2006  1   0
8   09/12/2006  5   0
9   16/12/2006  45  0
10  30/12/2006  1   1

因此,接收@PageNumber@DefaultLineNumberPerPage 的查询(将等于14,但也许有一天会改变)。

您能帮我设计这个查询或 SQL 函数吗?

谢谢!

【问题讨论】:

  • 您可以将您的数据作为文本发布吗?我们不能从图片中复制/粘贴。这是 mySql 还是 Sql-Server 以及什么版本?您需要接收@page 参数的查询吗?
  • 单个查询不会执行此操作。您需要对(至少)上一页 SUM 进行单独查询。此外,更好的架构是只存储页码或页面 ID 而不是最后一行指示符。
  • Sql server 版本?因此,如果您有 @DefaultLineNumberPerPage = 10 并且在下一个 LASTLINEOFPAGE = 1 之前有 15 行,那么您只显示 10 行和下一页 5?
  • 没错:10行,然后是下一页的5行。 SQL Server 2012 版

标签: sql sql-server sql-server-2012 pagination


【解决方案1】:

样本数据

我添加了几行来说明当每页的行数多于@DefaultLineNumberPerPage 时它是如何工作的。在此示例中,我将使用 @DefaultLineNumberPerPage=5,您将看到额外页面是如何生成的。

DECLARE @T TABLE (ID int, dt date, VALUE int, LASTLINEOFPAGE bit);
INSERT INTO @T(ID, dt, VALUE, LASTLINEOFPAGE) VALUES
(1 , '2006-10-07', 10 , 0),
(2 , '2006-10-14', 12 , 0),
(3 , '2006-10-21', 4  , 1),
(4 , '2006-10-28', 6  , 0),
(5 , '2006-11-04', 8  , 1),
(6 , '2006-11-25', 125, 0),
(7 , '2006-12-02', 1  , 0),
(8 , '2006-12-09', 5  , 0),
(9 , '2006-12-16', 45 , 0),
(10, '2006-12-30', 1  , 1),

(16, '2007-01-25', 125, 0),
(17, '2007-02-02', 1  , 0),
(18, '2007-02-09', 5  , 0),
(19, '2007-02-16', 45 , 0),
(20, '2007-02-20', 1  , 0),
(26, '2007-02-25', 125, 0),
(27, '2007-03-02', 1  , 0),
(28, '2007-03-09', 5  , 0),
(29, '2007-03-10', 5  , 0),
(30, '2007-03-11', 5  , 0),
(31, '2007-03-12', 5  , 0),
(32, '2007-03-13', 5  , 1),

(41, '2007-10-07', 10 , 0),
(42, '2007-10-14', 12 , 0),
(43, '2007-10-21', 4  , 1);

查询

逐步、逐个 CTE 运行它并检查中间结果以了解它的作用。

CTE_FirstLines 将页面的第一行而不是最后一行的 FirstLineOfPage 标志设置为 1。

CTE_SimplePages 使用累积的SUM 来计算基于FirstLineOfPage 分页符的简单页码。

CTE_ExtraPages 使用ROW_NUMBER 除以@DefaultLineNumberPerPage 来计算额外的页码,如果页面的行数超过@DefaultLineNumberPerPage

CTE_CompositePages 将简单的页码与额外的页码组合成一个复合页“编号”。它假定原始 LASTLINEOFPAGE 标志之间的行数少于 1000 行。如果有可能有这么长的行序列,增加1000 常量并考虑为CompositePageNumber 列使用bigint 类型。

CTE_FinalPages 使用DENSE_RANK 为每个最后一页分配无间隙的连续编号。

DECLARE @DefaultLineNumberPerPage int = 5;
DECLARE @PageNumber int = 3;

WITH
CTE_FirstLines
AS
(
    SELECT
        ID,dt, VALUE, LASTLINEOFPAGE
        ,CAST(ISNULL(LAG(LASTLINEOFPAGE) 
            OVER (ORDER BY dt), 1) AS int) AS FirstLineOfPage
    FROM @T
)
,CTE_SimplePages
AS
(
    SELECT
        ID,dt, VALUE, LASTLINEOFPAGE, FirstLineOfPage
        ,SUM(FirstLineOfPage) OVER(ORDER BY dt 
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS SimplePageNumber
    FROM CTE_FirstLines
)
,CTE_ExtraPages
AS
(
    SELECT
        ID,dt, VALUE, LASTLINEOFPAGE, FirstLineOfPage, SimplePageNumber
        ,(ROW_NUMBER() OVER(PARTITION BY SimplePageNumber ORDER BY dt) - 1)
            / @DefaultLineNumberPerPage AS ExtraPageNumber
    FROM CTE_SimplePages
)
,CTE_CompositePages
AS
(
    SELECT
        ID,dt, VALUE, LASTLINEOFPAGE, FirstLineOfPage, SimplePageNumber, ExtraPageNumber
        ,SimplePageNumber * 1000 + ExtraPageNumber AS CompositePageNumber
    FROM CTE_ExtraPages
)
,CTE_FinalPages
AS
(
    SELECT
        ID,dt, VALUE, LASTLINEOFPAGE, FirstLineOfPage, SimplePageNumber, ExtraPageNumber
        ,CompositePageNumber
        ,DENSE_RANK() OVER(ORDER BY CompositePageNumber) AS FinalPageNumber
    FROM CTE_CompositePages
)
,CTE_Sum
AS
(
    SELECT
        ID,dt, VALUE, LASTLINEOFPAGE, FirstLineOfPage, SimplePageNumber, ExtraPageNumber
        ,CompositePageNumber
        ,FinalPageNumber
        ,SUM(Value) OVER(ORDER BY FinalPageNumber, dt
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS SumCumulative
    FROM CTE_FinalPages
)
SELECT
    ID,dt, VALUE, LASTLINEOFPAGE, FirstLineOfPage, SimplePageNumber, ExtraPageNumber
    ,CompositePageNumber
    ,FinalPageNumber
    ,SumCumulative
FROM CTE_Sum
--  WHERE FinalPageNumber = @PageNumber
ORDER BY dt
;

最终WHERE 过滤器被注释掉的结果

以下是包含所有中间列的完整结果,用于说明查询的工作原理。

+----+------------+-------+-----+-----+--------+-------+-----------+-------+------------+
| ID |     dt     | VALUE | Lst | Fst | Simple | Extra | Composite | Final | TotalValue |
+----+------------+-------+-----+-----+--------+-------+-----------+-------+------------+
|  1 | 2006-10-07 |    10 |   0 |   1 |      1 |     0 |      1000 |     1 |         10 |
|  2 | 2006-10-14 |    12 |   0 |   0 |      1 |     0 |      1000 |     1 |         22 |
|  3 | 2006-10-21 |     4 |   1 |   0 |      1 |     0 |      1000 |     1 |         26 |
|  4 | 2006-10-28 |     6 |   0 |   1 |      2 |     0 |      2000 |     2 |         32 |
|  5 | 2006-11-04 |     8 |   1 |   0 |      2 |     0 |      2000 |     2 |         40 |
|  6 | 2006-11-25 |   125 |   0 |   1 |      3 |     0 |      3000 |     3 |        165 |
|  7 | 2006-12-02 |     1 |   0 |   0 |      3 |     0 |      3000 |     3 |        166 |
|  8 | 2006-12-09 |     5 |   0 |   0 |      3 |     0 |      3000 |     3 |        171 |
|  9 | 2006-12-16 |    45 |   0 |   0 |      3 |     0 |      3000 |     3 |        216 |
| 10 | 2006-12-30 |     1 |   1 |   0 |      3 |     0 |      3000 |     3 |        217 |
| 16 | 2007-01-25 |   125 |   0 |   1 |      4 |     0 |      4000 |     4 |        342 |
| 17 | 2007-02-02 |     1 |   0 |   0 |      4 |     0 |      4000 |     4 |        343 |
| 18 | 2007-02-09 |     5 |   0 |   0 |      4 |     0 |      4000 |     4 |        348 |
| 19 | 2007-02-16 |    45 |   0 |   0 |      4 |     0 |      4000 |     4 |        393 |
| 20 | 2007-02-20 |     1 |   0 |   0 |      4 |     0 |      4000 |     4 |        394 |
| 26 | 2007-02-25 |   125 |   0 |   0 |      4 |     1 |      4001 |     5 |        519 |
| 27 | 2007-03-02 |     1 |   0 |   0 |      4 |     1 |      4001 |     5 |        520 |
| 28 | 2007-03-09 |     5 |   0 |   0 |      4 |     1 |      4001 |     5 |        525 |
| 29 | 2007-03-10 |     5 |   0 |   0 |      4 |     1 |      4001 |     5 |        530 |
| 30 | 2007-03-11 |     5 |   0 |   0 |      4 |     1 |      4001 |     5 |        535 |
| 31 | 2007-03-12 |     5 |   0 |   0 |      4 |     2 |      4002 |     6 |        540 |
| 32 | 2007-03-13 |     5 |   1 |   0 |      4 |     2 |      4002 |     6 |        545 |
| 41 | 2007-10-07 |    10 |   0 |   1 |      5 |     0 |      5000 |     7 |        555 |
| 42 | 2007-10-14 |    12 |   0 |   0 |      5 |     0 |      5000 |     7 |        567 |
| 43 | 2007-10-21 |     4 |   1 |   0 |      5 |     0 |      5000 |     7 |        571 |
+----+------------+-------+-----+-----+--------+-------+-----------+-------+------------+

要仅获得一个给定页面,请取消注释最终 SELECT 中的 WHERE 过滤器。

最终WHERE 过滤器的结果

+----+------------+-------+-----+-----+--------+-------+-----------+-------+------------+
| ID |     dt     | VALUE | Lst | Fst | Simple | Extra | Composite | Final | TotalValue |
+----+------------+-------+-----+-----+--------+-------+-----------+-------+------------+
|  6 | 2006-11-25 |   125 |   0 |   1 |      3 |     0 |      3000 |     3 |        165 |
|  7 | 2006-12-02 |     1 |   0 |   0 |      3 |     0 |      3000 |     3 |        166 |
|  8 | 2006-12-09 |     5 |   0 |   0 |      3 |     0 |      3000 |     3 |        171 |
|  9 | 2006-12-16 |    45 |   0 |   0 |      3 |     0 |      3000 |     3 |        216 |
| 10 | 2006-12-30 |     1 |   1 |   0 |      3 |     0 |      3000 |     3 |        217 |
+----+------------+-------+-----+-----+--------+-------+-----------+-------+------------+

最后一行中的TotalValue 为您提供了您希望在页面底部显示的总页面价值。如果您将 this 页面上的所有值相加 (125+1+5+45+1 = 177) 并从最后一个 TotalValue (217-177 = 40) 中减去它,您将得到总数您想在页面顶部显示的以前的页面。您最好在客户端上进行这些计算。

【讨论】:

  • 看起来很棒!谢谢 !我将在拥有数千行代码的真实数据库上进行尝试,以检查时间响应。
  • @user1592596,看看它如何处理真实数据会很有趣。 (date) include (VALUE, LASTLINEOFPAGE) 上的索引应该会有所帮助。
  • 如何实现这个索引?是否有必要在 ID 字段上实现索引,还是默认情况下是这样? (我不是 SQL Server 专家)
  • @user1592596,索引由CREATE INDEX语句创建。像这样:CREATE NONCLUSTERED INDEX [IX_Date] ON [dbo].[TableName] ( [Date] ASC ) INCLUDE ([VALUE], [LASTLINEOFPAGE]) 。另一方面,几千行太小了。您可能根本不需要任何索引。
  • 这似乎与 4000 行效果很好!非常感谢。最后一个问题:我想将所有行的 MAX(FinalPageNumber) 作为单独的列添加(称为“MaxPageNumber”的新列)。当然,每一行里面的值都是一样的,但是我需要稍后在我的 C# 代码中读取这个值。在最后一个查询中,我尝试了 SELECT ID,dt, VALUE, LASTLINEOFPAGE, MAX(FinalPageNumber) 但这似乎更复杂
【解决方案2】:

我有一个部分解决方案。仍然不计算默认页面大小,但可以给你一个想法。所以让我知道你的想法。希望您熟悉 CTE。测试每个步骤,以便查看部分结果。

SQL Demo

WITH cte as (
    SELECT [ID], [DATE], [VALUE], [LASTLINEOFPAGE],
           SUM([VALUE]) OVER (ORDER BY [ID]) as Total,
           SUM([LASTLINEOFPAGE]) OVER (ORDER BY [ID]) as page_group
    FROM Table1
),
pages as (
    SELECT c1.[ID], c1.[Total],
           CASE WHEN c1.[ID] = 1         THEN 0
                WHEN c1.[ID] = m.[minID] THEN c1.[page_group] -1
                                         ELSE c1.[page_group]
           END as [page_group]
    FROM cte as c1
    JOIN (SELECT [page_group], MIN([ID]) as minID
          FROM cte
          GROUP BY [page_group]) m
      ON c1.[page_group] = m.[page_group]
)
SELECT c.[ID], c.[DATE], c.[VALUE], c.[LASTLINEOFPAGE],
       (SELECT MAX([Total])
        FROM pages p2
        WHERE p2.[page_group] = p.[page_group]) as [Total],
       p.[page_group]

FROM cte c
JOIN pages p
  ON c.[ID] = p.[id]

如您所见,总数和页面位于附加列中,您不应在应用中显示这些内容

【讨论】:

  • 非常感谢胡安卡洛斯!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-07
  • 2010-12-17
相关资源
最近更新 更多