事先找出查询有多少条记录

Hik*_*ari 3 sql-server optimization sql-server-2012 query-performance

我在 ExtJS 中开发了 SQL Server 2012 中查询结果的报告。

我将 Ext.Grid 与分页一起使用,因此我可以使用偏移量来限制查询将返回的记录数量。但是为了让这个 ExtJS 功能起作用,我仍然必须向它提供给定查询所具有的记录总数。

有些报告在白天使用过滤器和数据更改,因此我只能通过运行查询来知道总数。这导致运行它两次:一次使用 count(*) 获得总计数,再次获得正确的数据。

有没有办法找到查询的总数,即使它有偏移命令,而不必像我现在所做的那样运行它两次,也不必循环遍历所有记录?

Sol*_*zky 5

不幸的是,在查询完成之前不知道记录总数。这意味着您需要以某种方式让不受限制的查询完成,然后获取行的子集。

一种方法是添加COUNT(*) OVER () AS [TotalRows]到 SELECT 列表。这会将值作为一列返回,每一行都重复相同的值,但仍然如此。

SELECT [object_id], [schema_id], [name], [type_desc], COUNT(*) OVER () AS [TotalRows]
FROM sys.objects
ORDER BY [object_id] ASC OFFSET 10 ROWS FETCH NEXT 7 ROWS ONLY;
Run Code Online (Sandbox Code Playgroud)

当然,这不仅仅是访问可免费获得的值。只需将上述查询的执行计划与没有COUNT(*)..表达式的同一事物的计划进行比较:

SELECT [object_id], [schema_id], [name], [type_desc]
FROM sys.objects
ORDER BY [object_id] ASC OFFSET 10 ROWS FETCH NEXT 7 ROWS ONLY;
Run Code Online (Sandbox Code Playgroud)

我想出的另一种方法在我对 Stack Overflow 上一个几乎相同的问题的回答中有详细描述:

TSQL:有没有办法限制返回的行数并计算没有限制返回的总数(不将其添加到每一行)?

该问题和这个问题之间的区别在于,另一个问题特别不想将值作为它正在计算的结果集中的列返回。此外,另一个问题更关注“限制”方面,而这个问题至少与“偏移”有关,如果不是两者兼而有之的话。但是添加“偏移”部分并不困难:只需在返回所需结果的循环之前添加一个循环即可执行如下操作:

// assume an input param or variable of: int Offset;

int _RowCounter = 0;
while (Reader.Read() && ++_RowCounter < Offset);
Run Code Online (Sandbox Code Playgroud)

只是为了好玩,我想我会尝试另一种机制,它可以运行查询并在不检索行的情况下获取行数:CURSOR(是的,EVIL CURSOR)。@@CURSOR_ROWS调用后会填充该变量OPEN,即使您从未有FETCH任何行。

在下面(我不知道它的性能如何)中,我使用FETCH ABSOLUTE x来处理“偏移”方面,因为不需要循环获取那些初始行只是为了不使用它们。但是使用ABSOLUTE不允许使用FAST_FORWARD和等选项FORWARD_ONLY。无法使用FAST_FORWARD,然后我使用了STATIC,但必须对其进行测试,以查看添加是否STATIC真的有帮助或伤害。

现在,获得所需结果的唯一方法是将它们存储在表变量中(或临时表也可以,但我认为在这种情况下使用表变量更好),以便它们可以作为单个结果集返回。这样做FETCH没有INTO子句将返回不同的结果集每次通话(最有可能的不良)。

// assume an input param or variable of: int Offset;

int _RowCounter = 0;
while (Reader.Read() && ++_RowCounter < Offset);
Run Code Online (Sandbox Code Playgroud)

考虑到 aSTATIC CURSOR将其结果保存到 tempdb 中,以下在操作上基本相同,但更直接一些:

DECLARE @Offset INT = 10,
        @Limit INT = 7;

DECLARE cursed CURSOR LOCAL READ_ONLY STATIC -- can't use FAST_FORWARD or FORWARD_ONLY :(
FOR  SELECT so.[object_id], so.[schema_id], so.name, so.type_desc
     FROM   sys.objects so
     ORDER BY so.[object_id] ASC;

DECLARE @object_id INT,
        @schema_id INT,
        @name sysname,
        @type_desc NVARCHAR(60);

DECLARE @RowCounter INT = 0,
        @StartRow INT = (@Offset + 1); -- Offset is how many rows to skip

DECLARE @Results TABLE ([object_id] INT NOT NULL, [schema_id] INT NOT NULL,
                        [name] sysname NOT NULL, [type_desc] NVARCHAR(60) NOT NULL);

OPEN cursed; -- execute the query

SELECT @@CURSOR_ROWS AS [RowCountBeforeRetrievingAnyRows];

FETCH ABSOLUTE @StartRow -- position cursor at beginning of desired range
FROM  cursed
INTO  @object_id, @schema_id, @name, @type_desc;

WHILE (@@FETCH_STATUS = 0 AND @RowCounter < @Limit)
BEGIN
  INSERT INTO @Results ([object_id], [schema_id], [name], [type_desc])
  VALUES (@object_id, @schema_id, @name, @type_desc);

  SET @RowCounter += 1;

  FETCH NEXT
  FROM  cursed
  INTO  @object_id, @schema_id, @name, @type_desc;
END;

CLOSE cursed;
DEALLOCATE cursed;

-- return desired range of rows
SELECT * FROM @Results;


-----------------------------------------------
-- check the actual data to see if it worked
SELECT [object_id], [schema_id], [name], [type_desc], COUNT(*) OVER () AS [TotalRows]
FROM sys.objects
ORDER BY [object_id] ASC OFFSET @Offset ROWS FETCH NEXT @Limit ROWS ONLY;
Run Code Online (Sandbox Code Playgroud)