2011-02-26 20 views
2

我试图做分页在SQL Server存储过程,就像这样:保留合计结果信息虽然分页在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 NOT NULL)
B(PK,SMALLINT NOT NULL)
C(PK,SMALLINT NOT NULL)

回答

3

您可以使用窗口集合函数。即下面的例子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 
+0

我忘记了窗口集合,+1。 – 2011-02-26 18:11:39

+0

嗯...好主意。所以,用我的方法,我可以将全部结果放在一个变量中,但我必须使用临时表。使用你的方法,你可以使用CTE,但是你必须调用Count()来获得总结果,而不是仅仅获取@@ RowCount中已有的结果。而且你必须返回结果集中每一行的Count字段......我真的不确定从性能的角度来看这是否更好。 – Tedderz 2011-02-26 18:24:17

+1

@Tedderz - 从性能POV我不确定哪个最好。使用窗口化聚合函数为计划添加了一个假脱机,并且往往会增加很多逻辑读取次数。如果有一个避免假脱机的计划会更好,但到目前为止我不认为这是可能的。 – 2011-02-26 18:36:35

3

据我所知,这是不可能既要返回行和与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 
+0

+1这确实意味着2种排序操作,但可能有更好的执行计划,我的答案。 – 2011-02-26 20:21:48

+0

@Martin你太谦虚 - 你的代码应该有一个更好的计划,即使这是一个时髦。两者都需要一个单独的窗口,但这需要对每一行进行编号并执行棘手的数学运算。 +1两者 – RichardTheKiwi 2011-02-26 21:51:27