Sql [tempdb])但随后它会执行一个额外的步骤,选择所需的记录子集(这不是一个真正的问题,因为它应该仍然在缓冲池中)以返回到实际表中。更糟糕的是,第二步实际上是双I/O,因为该操作也表示在实际表所在的数据库的事务日志中。但是等等,还有更多的问题:下一次运行查询怎么样?你需要清理这张真正的桌子。无论是通过DELETE还是TRUNCATE TABLE,事务日志中都会显示另一个操作(基于使用这两个操作中的哪一个操作的表示量),加上额外操作所花费的额外时间。另外,我们不要忘记从插入的表中选择子集到实际表中的步骤:它没有机会使用索引,因为您无法为插入的表和删除的表编制索引。并不是说您总是希望向临时表添加索引,但有时它会有所帮助(取决于具体情况),您至少可以选择

Sql [tempdb])但随后它会执行一个额外的步骤,选择所需的记录子集(这不是一个真正的问题,因为它应该仍然在缓冲池中)以返回到实际表中。更糟糕的是,第二步实际上是双I/O,因为该操作也表示在实际表所在的数据库的事务日志中。但是等等,还有更多的问题:下一次运行查询怎么样?你需要清理这张真正的桌子。无论是通过DELETE还是TRUNCATE TABLE,事务日志中都会显示另一个操作(基于使用这两个操作中的哪一个操作的表示量),加上额外操作所花费的额外时间。另外,我们不要忘记从插入的表中选择子集到实际表中的步骤:它没有机会使用索引,因为您无法为插入的表和删除的表编制索引。并不是说您总是希望向临时表添加索引,但有时它会有所帮助(取决于具体情况),您至少可以选择,sql,sql-server,sql-server-2008,tsql,sqlclr,Sql,Sql Server,Sql Server 2008,Tsql,Sqlclr,过于复杂:当两个进程需要同时运行查询时会发生什么?如果它们共享要转储到的同一个实表,然后为最终输出选择出,则需要添加另一列来区分SPID。它可能是@@SPID。或者它可以是在调用实际表中的初始INSERT之前创建的GUID(这样就可以通过CONTEXT\u INFO()或临时表将其传递给而不是触发器)。无论值是什么,一旦选择了最终输出,它都将用于执行DELETE操作。如果不明显,这一部分会影响前面项目符号中提到的性能问题:TRUNCATE TABLE无法使用,因为它清除了整个表,留下DELETE

过于复杂:当两个进程需要同时运行查询时会发生什么?如果它们共享要转储到的同一个实表,然后为最终输出选择出,则需要添加另一列来区分SPID。它可能是
@@SPID
。或者它可以是在调用实际表中的初始
INSERT
之前创建的GUID(这样就可以通过
CONTEXT\u INFO()
或临时表将其传递给
而不是
触发器)。无论值是什么,一旦选择了最终输出,它都将用于执行
DELETE
操作。如果不明显,这一部分会影响前面项目符号中提到的性能问题:
TRUNCATE TABLE
无法使用,因为它清除了整个表,留下
DELETE FROM dbo.RealTable WHERE ProcessID=@WhateverID作为唯一选项

现在,公平地说,可以从触发器本身中进行最终选择。这将减少一些效率低下的情况,因为数据永远不会进入实际表,也永远不需要删除。它还减少了过度的复杂性,因为不需要通过SPID分离数据。但是,这是一个非常有时间限制的解决方案,因为在SQL Server的下一个版本中,从触发器中返回结果的功能将不再存在,因此,以下内容的MSDN页面如是说:

此功能将在下一版本的Microsoft SQL Server中删除。请勿在新的开发工作中使用此功能,请尽快修改当前使用此功能的应用程序。建议将此值设置为1


唯一实际的方法:

  • 查询一次
  • 获取行的子集
  • 并且仍然获得完整结果集的总行数
就是使用.Net。如果程序是从应用程序代码调用的,请参阅底部的“编辑2”。如果您希望能够通过即席查询随机运行各种存储过程,那么它必须是一个SQLCLR存储过程,这样它就可以是通用的,并且可以用于任何查询,因为存储过程可以返回动态结果集,而函数不能。proc至少需要3个参数:

  • @QueryToExec NVARCHAR(最大值)
  • @RowsToReturn INT
  • @全行整数输出
其思想是使用“Context Connection=true;”来利用内部/进程内连接。然后执行以下基本步骤:

  • 调用ExecuteDataReader()
  • 在读取任何行之前,请执行
    GetSchemaTable()
  • 从SchemaTable中可以获得结果集字段名和数据类型
  • 从结果集结构构建一个
    SqlDataRecord
  • 使用该
    SqlDataRecord
    调用
    SqlContext.Pipe.SendResultsStart(\u DataRecord)
  • 现在开始调用
    Reader.Read()
  • 对于您调用的每一行:
  • Reader.GetValues()
  • DataRecord.SetValues()
  • SqlContext.Pipe.SendResultRow(\u DataRecord)
  • RowCounter++
  • 而不是执行典型的“
    while(Reader.Read())
    ”,而是包含@RowsToReturn参数:
    while(Reader.Read()&&RowCounter
  • 在while循环之后,调用
    SqlContext.Pipe.SendResultsEnd()
    关闭结果集(您正在发送的结果集,而不是正在读取的结果集)
  • 然后执行第二个while循环,循环遍历结果的其余部分,但从未获得任何字段: while(Reader.Read()) { 行计数器++; }
  • 然后只需设置
    TotalRows=RowCounter
    将传回完整结果集的行数,即使您只返回了前n行:)
  • 我不确定这对temp table方法、dual call方法,甚至@M.Ali的方法(我也尝试过,有点像,但问题是不以列形式发送值)的性能如何,但它应该很好,并且确实按照要求完成了任务

    编辑:
    更好!另一个选项(上述C建议的变体)是使用T-SQL存储过程中的
    @@ROWCOUNT
    ,作为
    输出
    参数发送,而不是在
    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;
    
    然后,在应用程序代码中,为“@RowCount”创建一个新的SqlParameter,Direction=Output。除最后两个步骤(10和11)更改为:

  • 只需调用
    Reader.Close()
  • 不使用RowCounter变量,而是设置
    TotalRows=(int)rowconn
    
    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 
    
    select count(*) 
    from ...
    where ...
    select top x 
    from ...
    where ... 
    order by ...
    
    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;