我正在努力更新当前最多选择n行的存储过程,如果返回的行= n,则执行没有限制的选择计数,然后返回原始选择和受影响的总行数。 / p>
有点像:
SELECT TOP (@rowsToReturn)
A.data1,
A.data2
FROM
mytable A
SET @maxRows = @@ROWCOUNT
IF @rowsToReturn = @@ROWCOUNT
BEGIN
SET @maxRows = (SELECT COUNT(1) FROM mytableA)
END
我希望将此缩减为单个select语句。基于this question,COUNT(*) OVER()
允许这样做,但它放在每一行而不是输出参数中。也许类似于MYSQL中的FOUND_ROWS()
,例如@@ TOTALROWCOUNT等。
作为旁注,由于实际的select有一个order by,数据库需要已经遍历整个set(以确保它获得正确的前n个有序记录),所以数据库应该已经有了这个算在某处。
答案 0 :(得分:7)
正如@MartinSmith在对这个问题的评论中提到的那样,没有直接(即纯T-SQL)方法来获取将返回的行总数,同时限制它。过去我做过以下方法:
@@ROWCOUNT
(总计)ROW_NUBMER() AS [ResultID]
SELECT TOP (n) FROM #Temp ORDER BY [ResultID]
或类似的东西当然,这里的缺点是你有这些记录进入临时表的磁盘I / O成本。把[tempdb]
放在SSD上? :)
我还经历了"首先使用相同的查询其余部分运行COUNT(*),然后运行常规的SELECT"方法(由@Blam倡导),它不是一个免费的"重新运行查询:
COUNT(*)
(因此不返回任何字段)时,优化器只需要担心JOIN,WHERE,GROUP BY和ORDER BY子句的索引。但是当你想要一些实际数据时,可以更改执行计划,特别是如果用于获取COUNT(*)的索引不是"覆盖"对于SELECT列表中的字段。我并不是说这种方法不起作用,但我认为问题中只有条件COUNT(*)
的方法对系统的压力要小得多。
@Gordon倡导的方法实际上与我上面描述的临时表方法非常相似:它将完整的结果集转储到[tempdb](INSERTED
表在[tempdb]中)以获得完整@@ROWCOUNT
然后它获得一个子集。在缺点方面,INSTEAD OF TRIGGER方法是:
很多设置更多工作(如10x - 20x以上):你需要一个真实的表来表示每个不同的结果集,你需要一个触发器,触发器需要要么动态构建,要么获取从某个配置表返回的行数,或者我想它可以从CONTEXT_INFO()
或临时表中获取。尽管如此,整个过程还是有很多步骤而且很复杂。
非常效率低下:首先它将完整的结果集转储到表中(即进入INSERTED
表 - 它位于{{1}中的工作量相同但是它会做一个额外的步骤来选择所需的记录子集(不是真正的问题,因为它应该仍然在缓冲池中)以返回到真实的表中。更糟糕的是,第二步实际上是双I / O,因为操作也在真实表存在的数据库的事务日志中表示。但是等等,还有更多:下一次查询运行怎么样?你需要清除这个真正的表格。无论是通过[tempdb]
还是DELETE
,它都是在事务日志中显示的另一个操作(基于使用这两个操作中的哪一个的表示量),另外还有额外的时间用于附加操作。并且,我们不要忘记从TRUNCATE TABLE
中选择子集到真实表中的步骤:它没有机会使用索引,因为您无法索引INSERTED
和INSERTED
个表格。并不是说您总是希望在临时表中添加索引,但有时它会有所帮助(取决于具体情况)并且您至少可以选择。
过于复杂:当两个进程需要同时运行查询时会发生什么?如果他们共享同一个真实表以进行转储,然后选择输出最终输出,那么需要添加另一个列来区分SPID。它可能是DELETED
。或者它可以是在调用真实表的初始@@SPID
之前创建的GUID(以便可以通过INSERT
或临时表传递给INSTEAD OF
触发器)。无论值是什么,一旦选择了最终输出,它将用于执行CONTEXT_INFO()
操作。如果不明显,此部分会影响之前项目符号中出现的性能问题:DELETE
无法使用,因为它会清除整个表格,只留下TRUNCATE TABLE
。
现在,公平地说,可能从触发器本身内进行最终的SELECT。这将减少一些低效率,因为数据永远不会进入真实表,然后也永远不需要删除。它还减少了过度复杂化,因为不需要通过SPID分离数据。但是,这是一个非常时间限制的解决方案,因为在触发器内返回结果的能力将在下一版本的SQL Server中再次出现,所以说{{3}的MSDN页面}}:
此功能将在下一版本的Microsoft SQL Server中删除。请勿在新的开发工作中使用此功能,并尽快修改当前使用此功能的应用程序。我们建议您将此值设置为1。
唯一可行的方法:
是使用.Net。如果从应用程序代码调用过程,请参阅"编辑2"在底部。如果您希望能够通过即席查询随机运行各种存储过程,那么它必须是SQLCLR存储过程,以便它可以是通用的并适用于任何查询,因为存储过程可以返回动态结果集,而函数则不能。 proc需要至少3个参数:
这个想法是使用" Context Connection = true;"使用内部/进程中的连接。然后,您可以执行以下基本步骤:
DELETE FROM dbo.RealTable WHERE ProcessID = @WhateverID;
ExecuteDataReader()
GetSchemaTable()
SqlDataRecord
来调用SqlDataRecord
SqlContext.Pipe.SendResultsStart(_DataRecord)
Reader.Read()
Reader.GetValues()
DataRecord.SetValues()
SqlContext.Pipe.SendResultRow(_DataRecord)
RowCounter++
",而是包含@RowsToReturn参数:while (Reader.Read())
while(Reader.Read() && RowCounter < RowsToReturn.Value)
关闭结果集(您要发送的结果集,而不是您正在阅读的结果集)SqlContext.Pipe.SendResultsEnd()
,它将传回完整结果集的行数,即使您只返回了它的前n行:)不确定这是如何针对临时表方法,双重调用方法,甚至是@ M.Ali的方法(我也尝试了类似的方法),但问题是针对而不是将值作为列发送,但它应该没问题,并按要求完成任务。
修改强>
更好!另一个选项(上述C#建议的变体)是使用T-SQL存储过程中的TotalRows = RowCounter;
,作为@@ROWCOUNT
参数发送,而不是循环遍历OUTPUT
中的其余行。 {1}}。因此存储过程类似于:
SqlDataReader
然后,在应用程序代码中,为&#34; @ RowCount&#34;创建一个新的SqlParameter,Direction = Output。上面编号的步骤保持不变,除了最后两个(10和11),它们变为:
CREATE PROCEDURE SchemaName.ProcName
(
@Param1 INT,
@Param2 VARCHAR(05),
@RowCount INT OUTPUT = -1 -- default so it doesn't have to be passed in
)
AS
SET NOCOUNT ON;
{any ol' query}
SET @RowCount = @@ROWCOUNT;
Reader.Close()
我试过这个并且确实有效。但到目前为止,我没有时间来测试其他方法的性能。
编辑2:
如果从应用层调用T-SQL存储过程(即不需要临时执行),那么这实际上是上述C#方法的更简单的变化。在这种情况下,您不必担心TotalRows = (int)RowCountOutputParam.Value;
或SqlDataRecord
方法。假设您已设置SqlContext.Pipe
设置以撤回结果,您只需:
SqlDataReader
SET @RowCount = @@ROWCOUNT;
SqlParameter
的循环,以便在撤回所需金额后停止检索结果。while(Reader.Read() && RowCounter < RowsToReturn)
)就在那时,就像第一个&#34; EDIT&#34;在上面,只需关闭TOP (n)
并获取OUTPUT参数的SqlDataReader
:.
答案 1 :(得分:2)
这个怎么样....
DECLARE @N INT = 10
;WITH CTE AS
(
SELECT
A.data1,
A.data2
FROM mytable A
)
SELECT TOP (@N) * , (SELECT COUNT(*) FROM CTE) Total_Rows
FROM CTE
最后一列将填充它在没有TOP子句的情况下返回的总行数。
您的要求的问题是,您期望一个SINGLE select语句返回一个表和一个标量值。这是不可能的。
单个select语句将返回表或标量值。或者你可以有两个单独的选择,一个返回一个标量值,另一个返回一个标量。选择是你的:))
答案 2 :(得分:1)
仅仅因为你认为TSQL应该有一个行数,因为它的排序并不意味着它。如果确实如此,它目前还没有与外界分享。
你缺少的是这是非常有效的
select count(*)
from ...
where ...
select top x
from ...
where ...
order by ...
使用count(*)除非查询非常丑陋,否则这些索引应该在内存中。
它必须执行计数才能根据什么进行排序? 您是否真的评估过任何查询计划? 如果TSQL必须执行排序,请解释以下内容 当第二个必须进行计数时,为什么计数(*)是成本的100%? 在第二个查询计划的哪个位置有一个可以自由计算的机会? 如果这些查询计划都需要计算,为什么这些查询计划如此不同?
答案 3 :(得分:0)
我认为有一种神秘的方法可以做你想要的。它涉及触发器和非临时表。而且,我应该提一下,虽然我已经实现了每一件(出于不同的目的),但我从未将它们组合在一起用于此目的。
这个想法从这个Stack Overflow question开始。根据此消息来源,@@ROWCOUNT
计算尝试插入的数量,即使它们确实没有发生。现在,我必须承认,仔细阅读可用文档似乎没有触及这个主题,所以这可能是也可能不是“正确”的行为。这种方法依赖于这个“问题”。
所以,你可以做你想做的事情:
@maxRows
进入表格。@@ROWCOUNT
。select
请注意,您可以使用动态SQL创建表和触发器。您也可以创建一次,并使触发器从某种参数表中读取@maxRows
值。如前所述,这需要是一个支持触发器的真实表。