Law*_*gle 9 sql t-sql sql-server sqlclr sql-server-2008
我正在努力更新当前选择最多n行的存储过程,如果返回的行= n,则执行没有限制的选择计数,然后返回原始选择和受影响的总行数.
有点像:
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
Run Code Online (Sandbox Code Playgroud)
我想把它减少到一个select语句.基于这个问题,COUNT(*) OVER()允许这个,但是它被放在每一行而不是输出参数中.也许像FOUND_ROWS()MYSQL中的东西,比如@@ TOTALROWCOUNT等.
作为旁注,由于实际选择具有order by,数据库将需要已经遍历整个集合(以确保它获得正确的前n个有序记录),因此数据库应该已经在某处具有此计数.
正如@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方法是:
很多更多的工作来设置(在10倍- 20倍以上):你需要一个真正的表来表示每个不同的结果集,你需要一个触发,触发需要既可以动态构建,或者得到的行数返回从一些配置表,或者我想它可以从CONTEXT_INFO()临时表中获取它.尽管如此,整个过程还是相当多的步骤而且令人费解.
效率非常低:首先它将完整的结果集转储到表中(即进入INSERTED表中 - 存在[tempdb])进行相同的工作量但是然后它会选择所需的记录子集(不是真正的问题)这应该仍然在缓冲池中)回到真正的表中.更糟糕的是,第二步实际上是双I/O,因为操作也在真实表存在的数据库的事务日志中表示.但是等等,还有更多:下一次查询运行怎么样?你需要清除这个真正的表格.无论是via DELETE还是TRUNCATE TABLE,它是在事务日志中显示的另一个操作(基于使用这两个操作中的哪一个的表示量),另外还有额外的时间用于附加操作.并且,我们不要忘记从INSERTED实际表中选择子集的步骤:它没有机会使用索引,因为您无法索引INSERTED和DELETED表.并不是说您总是希望在临时表中添加索引,但有时它会有所帮助(取决于具体情况)并且您至少有这个选择.
过于复杂:当两个进程需要同时运行查询时会发生什么?如果他们共享相同的真实表以进行转储,然后选择输出最终输出,则需要添加另一列来区分SPID.它可能是@@SPID.或者它可以是在INSERT调用实际表的初始值之前创建的GUID (以便它可以INSTEAD OF通过CONTEXT_INFO()或临时表传递给触发器).无论值是什么,DELETE一旦选择了最终输出,它将用于执行操作.如果不是很明显,这部分会影响前一篇文章中提出的性能问题:TRUNCATE TABLE不能使用,因为它会清除整个表格,DELETE FROM dbo.RealTable WHERE ProcessID = @WhateverID;这是唯一的选择.
现在,公平地说,可以在触发器本身内完成最终的SELECT.这将减少一些低效率,因为数据永远不会进入真实表,然后也永远不需要删除.它还减少了过度复杂化,因为不需要通过SPID分离数据.但是,这是一个非常有时间限制的解决方案,因为在触发器中返回结果的能力将在SQL Server的下一个版本中再次出现,所以说MSDN页面禁止触发服务器配置选项的结果:
此功能将在下一版本的Microsoft SQL Server中删除.请勿在新的开发工作中使用此功能,并尽快修改当前使用此功能的应用程序.我们建议您将此值设置为1.
唯一的实际方法:
是使用.Net.如果从应用程序代码调用过程,请参见底部的"编辑2".如果您希望能够通过即席查询随机运行各种存储过程,那么它必须是SQLCLR存储过程,以便它可以是通用的并适用于任何查询,因为存储过程可以返回动态结果集,而函数则不能.proc需要至少3个参数:
想法是使用"Context Connection = true;" 使用内部/进程中的连接.然后,您执行以下基本步骤:
ExecuteDataReader()GetSchemaTable()SqlDataRecordSqlDataRecord你打电话SqlContext.Pipe.SendResultsStart(_DataRecord)Reader.Read()Reader.GetValues()DataRecord.SetValues()SqlContext.Pipe.SendResultRow(_DataRecord)RowCounter++while (Reader.Read())",而是包含@RowsToReturn参数:while(Reader.Read() && RowCounter < RowsToReturn.Value)SqlContext.Pipe.SendResultsEnd()以关闭结果集(您要发送的结果集,而不是您正在阅读的结果集)TotalRows = RowCounter;哪个将传回完整结果集的行数,即使你只返回它的前n行:)不知道这对于临时表方法,双重调用方法,甚至是@ M.Ali的方法(我也尝试了类似,但问题是特定于不将值作为列发送)的表现如何,但它应该没问题,并按要求完成任务.
编辑:
更好!另一个选项(上述C#建议的变体)是使用@@ROWCOUNT来自T-SQL存储过程,作为OUTPUT参数发送,而不是循环通过其余的行SqlDataReader.所以存储过程类似于:
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;
Run Code Online (Sandbox Code Playgroud)
然后,在应用程序代码中,为"@RowCount"创建一个新的SqlParameter,Direction = Output.上面编号的步骤保持不变,除了最后两个(10和11),它们变为:
Reader.Close()TotalRows = (int)RowCountOutputParam.Value;我试过这个,它确实有效.但到目前为止,我没有时间来测试其他方法的性能.
编辑2:
如果从应用层调用T-SQL存储过程(即不需要临时执行),那么这实际上是上述C#方法的一个更简单的变体.在这种情况下,您无需担心SqlDataRecord或SqlContext.Pipe方法.假设您已经SqlDataReader设置了撤回结果,您只需要:
SET @RowCount = @@ROWCOUNT;在查询后立即进行SqlParameter具有Direction = Outputwhile(Reader.Read() && RowCounter < RowsToReturn)这样您就可以在撤回所需金额后停止检索结果.TOP (n))那时,就像上面第一个"编辑"中提到的那样,只需关闭SqlDataReader并获取.ValueOUTPUT参数:).
| 归档时间: |
|
| 查看次数: |
4031 次 |
| 最近记录: |