【问题标题】:Preserving Total Result Information While Paginating In SQL Server在 SQL Server 中分页时保留总结果信息
【发布时间】:2011-07-04 22:45:44
【问题描述】:

我正在尝试在 SQL Server 的存储过程中进行分页,如下所示:

/*Assign a row number to each row*/
SELECT
ROW_NUMBER() OVER (ORDER BY A, B, C ASC) AS ROW_NUMBER,
A, B, C
FROM ABC
WHERE ROW_NUMBER BETWEEN @startRecordNumber and @endRecordNumber

但是,我的调用代码还想知道分页前的原始结果集中有多少结果。所以,我把我的代码改成这样:

/*Put the results into a temp table first*/
SELECT
ROW_NUMBER() OVER (ORDER BY A, B, C ASC) AS ROW_NUMBER,
A, B, C
INTO #TOTAL_RESULTS
FROM ABC

/*Get the total results from @@RowCount*/
declare @totalResults bigint
set @totalResults = @@RowCount

/*Now just get the desired page from the temp table*/
SELECT
A, B, C
FROM #TOTAL_RESULTS
WHERE ROW_NUMBER BETWEEN @startRecordNumber and @endRecordNumber

DROP TABLE #TOTAL_RESULTS

这对我来说感觉很迂回。有什么方法可以在不制作临时表的情况下获得原始结果集的大小?也许只是一个公用表表达式?我似乎想不出办法。

如果重要的话,这里是 ABC 的架构:

ABC
A(PK,smallint 不为空)
B(PK,smallint 不为空)
C (PK, smallint 不为空)

【问题讨论】:

    标签: sql-server database pagination


    【解决方案1】:

    您可以使用窗口聚合函数。即COUNT(*) OVER() 下面的示例。

    ;WITH cte As
    (
    SELECT *, 
            ROW_NUMBER() OVER (ORDER BY number) AS RN,
            COUNT(*) OVER() AS Cnt
    FROM master..spt_values
    )
    SELECT *
    FROM cte 
    WHERE RN BETWEEN 101 and 200
    

    或者- 不是一个严肃的建议,但确实避免了线轴、工作台和双重排序:-)

    DECLARE @Spid INT = @@Spid
    DECLARE @TraceID INT
    
    DECLARE @maxfilesize BIGINT = 5
    DECLARE @filepath NVARCHAR(200) = N'C:\trace_' + LEFT(NEWID(),36)
    
    EXEC sp_trace_create @TraceID OUTPUT, 0, @filepath, @maxfilesize, NULL 
    
    exec sp_trace_setevent @TraceID, 146, 1, 1
    exec sp_trace_setevent @TraceID, 146, 22, 1
    exec sp_trace_setevent @TraceID, 146, 34, 1
    exec sp_trace_setevent @TraceID, 146, 51, 1
    exec sp_trace_setevent @TraceID, 146, 12, 1
    -- filter for spid
    EXEC sp_trace_setfilter @TraceID, 12, 0, 0, @Spid
    -- start the trace
    EXEC sp_trace_setstatus @TraceID, 1
    
    
    ;WITH cte AS
    (
    SELECT number, type, name, 
            ROW_NUMBER() OVER (ORDER BY number, type, name) AS RN
    FROM master..spt_values
    )
    SELECT * FROM cte
    WHERE RN BETWEEN 101 AND 200
    OR RN+1 =0 /*To stop a "TOP 200" getting added to the plan*/
    
    ;WITH  XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' as sql)
         SELECT  ActualRows
             FROM   fn_trace_getinfo(@TraceID) fn
                    CROSS APPLY fn_trace_gettable(CAST(value AS NVARCHAR(200)), 1)
                    CROSS APPLY (SELECT CAST(TextData AS XML) AS xPlan) x
                    CROSS APPLY (SELECT T.relop.value('@ActualRows', 'INT') AS ActualRows
                                 FROM   xPlan.nodes('//sql:RelOp[@LogicalOp="Segment"]/sql:RunTimeInformation/sql:RunTimeCountersPerThread') T(relop)) ca
             WHERE  property = 2
                    AND ObjectName<>'fn_trace_getinfo' AND TextData NOT LIKE '%ThisQuery%'  
    
    -- Stop the trace
    EXEC sp_trace_setstatus @TraceID, 0
    -- Close and delete the trace
    EXEC sp_trace_setstatus @TraceID, 2
    

    【讨论】:

    • 我忘了窗口聚合,+1。
    • 嗯...好主意。因此,通过我的方法,我可以将总结果保存在变量中,但我必须使用临时表。通过您的方法,您可以使用 CTE,但您必须调用 Count() 才能获得总结果,而不是仅仅获得 @@RowCount 中已有的结果。而且您必须为结果集中的每一行返回该 Count 字段...从性能的角度来看,我真的不确定这是否更好。
    • @Tedderz - 从性能 POV 来看,我也不确定哪个最好。使用窗口聚合函数会在计划中添加一个假脱机,并且往往会大量增加逻辑读取的数量。如果有一个避免线轴的计划会更好,但到目前为止我想不出这是否可能。
    • @Tedderz TBH 我认为您现有的方法可能会更好。窗口聚合方法的计划执行所有行编号,然后将行放入惰性线轴(无论如何这只是一个临时表 AFAIK),然后在线轴上执行count(*),然后使用count(*) 的结果作为额外的列附加。然后它按 row_number 对输出进行排序,然后最终在 100 到 200 之间对 RN 进行过滤。如果您要使用在 row_number 的标识列上索引的 #temp 表或表变量,您可以只使用索引进行选择。
    【解决方案2】:

    据我所知,SQL Server 不可能同时返回行和分配变量。因此,如果您希望将行数存储到变量中,则必须以一种或另一种方式拥有多个语句。

    但如果可以将总计作为列返回,并且表中的 A、B 和 C 列没有重复项,则可以返回总计,例如,如下所示:

    SELECT
      A, B, C,
      TotalResults = RowNumAsc + RowNumDesc - 1
    FROM (
      SELECT
        A, B, C,
        RowNumAsc = ROW_NUMBER() OVER (ORDER BY A, B, C),
        RowNumDesc = ROW_NUMBER() OVER (ORDER BY A DESC, B DESC, C DESC)
      FROM
    ) s
    WHERE RowNumAsc BETWEEN @startRecordNumber AND @endRecordNumber
    

    【讨论】:

    • +1 这确实意味着可能有 2 次排序操作,但可能有比我的答案更好的执行计划。
    • @Martin 你太谦虚了——你的代码应该有一个更好的计划,即使这个很时髦。两者都需要一个单独的窗口,但这个窗口需要对每一行进行编号并执行棘手的数学运算。两个+1
    猜你喜欢
    • 2010-09-05
    • 1970-01-01
    • 1970-01-01
    • 2011-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-23
    相关资源
    最近更新 更多