Hik*_*ari 3 sql-server optimization sql-server-2012 query-performance
我在 ExtJS 中开发了 SQL Server 2012 中查询结果的报告。
我将 Ext.Grid 与分页一起使用,因此我可以使用偏移量来限制查询将返回的记录数量。但是为了让这个 ExtJS 功能起作用,我仍然必须向它提供给定查询所具有的记录总数。
有些报告在白天使用过滤器和数据更改,因此我只能通过运行查询来知道总数。这导致运行它两次:一次使用 count(*) 获得总计数,再次获得正确的数据。
有没有办法找到查询的总数,即使它有偏移命令,而不必像我现在所做的那样运行它两次,也不必循环遍历所有记录?
不幸的是,在查询完成之前不知道记录总数。这意味着您需要以某种方式让不受限制的查询完成,然后获取行的子集。
一种方法是添加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)