SQL服务器模数运算符跳过大表上的每第n行

时间:2013-09-03 19:01:57

标签: sql sql-server sql-server-2012

我有一张100,000,000行的大表。我想从表格中选择每一行的第n行。我的第一直觉是使用这样的东西:

SELECT id,name FROM table WHERE id%125000=0

检索800行的偶数扩展(id是聚簇索引)

此技术适用于较小的数据集但使用较大的表时查询需要2.5分钟。我假设这是因为模数运算应用于每一行。是否有更优化的跳过行的方法?

4 个答案:

答案 0 :(得分:2)

时间不会进入模数运算本身,而是仅为您实际需要的每一行读取124,999个不必要的行(即表扫描或聚簇索引扫描)。

加速像这样的查询的唯一方法似乎是不合逻辑的:在该列上添加额外的非聚集索引([ID])。此外,您可能必须添加索引提示以强制它使用该索引。最后,它实际上可能不会更快,但是对于125,000+的模数,它应该是(虽然它永远不会真正快)。


如果您的ID不一定是连续的(任何删除的行几乎都会导致这种情况)并且您确实需要每个模数行,按ID顺序,那么您仍然可以使用上述方法,但您必须使用查询中的ROW_NUMBER() OVER(ORDER BY ID)重新排序模数操作的ID。

答案 1 :(得分:2)

您的查询假设ID是连续的(可能它们并非没有您意识到这一点......)。无论如何,你应该自己生成ID:

select *
from T
where ID in (0, 250000*1, 250000*2, ...)

也许您需要TVP来发送所有ID,因为有这么多。或者,您可以使用T-SQL或SQLCLR函数或数字表在服务器上生成ID。

此技术允许您执行索引搜索,并且是您可能生成的最快速度。它读取可能的最小数据量。

Modulo不是SARGable。如果微软想要它,SQL Server可以支持这个,但这是一个奇特的用例。他们永远不会制造模数SARGable,他们不应该。

答案 2 :(得分:1)

如果id在索引中,那么我正在考虑以下几点:

with ids as (
      select 1 as id
      union all
      select id + 125000
      from ids
      where id <= 100000000
  )
select ids.id,
       (select name from table t where t.id = ids.id) as name
from ids
option (MAXRECURSION 1000);

我认为这个公式将使用表格中的索引。

编辑:

当我考虑这种方法时,您实际上可以使用它来获取表中的实际随机ID,而不仅仅是均匀间隔的:

with ids as (
      select 1 as cnt,
             ABS(CONVERT(BIGINT,CONVERT(BINARY(8), NEWID()))) % 100000000 as id
      union all
      select cnt + 1, ABS(CONVERT(BIGINT,CONVERT(BINARY(8), NEWID()))) % 100000000
      from ids
      where cnt < 800
  )

select ids.id,
       (select name from table t where t.id = ids.id) as name
from ids
option (MAXRECURSION 1000);

实际随机数生成器的代码来自here

编辑:

由于SQL Server中的怪癖,您仍然可以获得非连续的ID,即使在您的方案中也是如此。这被接受answer解释了原因。简而言之,身份值不是一次分配一个,而是分组。服务器可能会失败,甚至会跳过未使用的值。

我想进行随机抽样的一个原因是帮助避免这个问题。据推测,在大多数系统中,上述情况相当罕见。您可以使用随机抽样生成900 ids。从这些中,您应该能够找到实际可用于样品的800个。

答案 3 :(得分:0)

 DECLARE @i int, @max int, @query VARCHAR(1000)
 SET @i = 0
 SET @max = (SELECT max(id)/125000 FROM Table1)
 SET @query = 'SELECT id, name FROM Table1 WHERE id in ('
 WHILE @i <= @max
 BEGIN
     IF @i > 0 SET @query = @query + ','
     SET @query = @query + CAST(@i*125000 as varchar(12))
     SET @i = @i + 1
 END
 SET @query = @query + ')'
 EXEC(@query)

编辑:

为避免在非连续ID情况下出现任何“漏洞”,您可以尝试以下方法:

DECLARE @i int, @start int, @id int, @max int, @query VARCHAR(1000)
SET @i = 0
SET @max = (SELECT max(id)/125000 FROM Table1)
SET @query = 'SELECT id, name FROM Table1 WHERE id in ('
WHILE @i <= @max
BEGIN
    SET @start = @i*125000
    SET @id = (SELECT TOP 1 id FROM Table1 WHERE id >= @start ORDER BY id ASC)
    IF @i > 0 SET @query = @query + ','
    SET @query = @query + CAST(@id as VARCHAR(12))
    SET @i = @i + 1
END
SET @query = @query + ')'
EXEC(@query)