通过基于另一个聚簇列的二进制搜索搜索列 - SQL Server查询

时间:2015-12-13 12:33:25

标签: sql sql-server

说,我有一个仅附加审计表,其中包含以下架构:

ColumnX, AuditRowId, AuditDTim
CLUSTERED INDEX ON AuditRowId

如何编写“高性能”SQL Server查询以查找特定COUNT(*)值之间的AuditDTim行?

这些是限制:

  • 我想使用SQL。没有数据迁移到BigData平台。
  • 我们无法将聚集索引更改为AuditDTim。假设legacyDb并且没有DDL访问权。
  • 我们无法向AuditDTim添加新索引。
  • 假设这是一张包含数十亿条记录的大表
  • AuditRowId和AuditDTim之间的关系 - 两者都是增量的,即,当AuditRowId增加时,AuditDTim增加或保持不变。

3 个答案:

答案 0 :(得分:2)

您应该能够在AuditRowId上使用两个二进制搜索。

一个用于查找与开始时间关联的ID,另一个用于查找与结束时间关联的ID,然后在这两个Id之间执行范围搜索。

基本思路的一个例子如下:SQL Fiddle(虽然我不保证它没有错误)

DECLARE @AuditDTimStart DATETIME = '2000-01-15',
        @AuditDTimEnd   DATETIME = '2000-01-20'

IF @AuditDTimEnd < @AuditDTimStart
  RAISERROR('Start date after end date',16,1)

DECLARE @AuditRowIdStart INT,
        @AuditRowIdEnd   INT,
        @AuditRowIdMin1  INT,
        @AuditRowIdMax1  INT,
        @AuditRowIdMin2  INT,
        @AuditRowIdMax2  INT

SELECT TOP 1 @AuditRowIdMin1 = AuditRowId,
             @AuditRowIdMin2 = AuditRowId,
             @AuditRowIdStart = -1 + CASE
                                       WHEN @AuditDTimStart < AuditDTim
                                         THEN AuditRowId
                                     END
FROM   YourTable
ORDER  BY AuditRowId

SELECT TOP 1 @AuditRowIdMax1 = AuditRowId,
             @AuditRowIdMax2 = AuditRowId,
             @AuditRowIdEnd = 1 + CASE
                                    WHEN @AuditDTimEnd > AuditDTim
                                      THEN AuditRowId
                                  END
FROM   YourTable
ORDER  BY AuditRowId DESC

WHILE @AuditRowIdStart IS NULL
  BEGIN
      -- Binary search to find latest row where AuditDTim < @AuditDTimStart
      SELECT TOP 1 @AuditRowIdMax1 = CASE
                                       WHEN AuditDTim >= @AuditDTimStart
                                         THEN AuditRowId
                                       ELSE @AuditRowIdMax1
                                     END,
                   @AuditRowIdMin1 = CASE
                                       WHEN AuditDTim < @AuditDTimStart
                                         THEN AuditRowId
                                       ELSE @AuditRowIdMin1
                                     END
      FROM   YourTable
      WHERE  AuditRowId <= @AuditRowIdMin1 + ( ( @AuditRowIdMax1 - @AuditRowIdMin1 ) / 2 )
      ORDER  BY AuditRowId DESC

      IF @AuditRowIdMax1 - @AuditRowIdMin1 <= 1
        SET @AuditRowIdStart = @AuditRowIdMin1;
  END

WHILE @AuditRowIdEnd IS NULL
  BEGIN
      -- Binary search to find earliest row where AuditDTim > @AuditRowIdEnd
      SELECT TOP 1 @AuditRowIdMax2 = CASE
                                       WHEN AuditDTim > @AuditDTimEnd
                                         THEN AuditRowId
                                       ELSE @AuditRowIdMax2
                                     END,
                   @AuditRowIdMin2 = CASE
                                       WHEN AuditDTim <= @AuditDTimEnd
                                         THEN AuditRowId
                                       ELSE @AuditRowIdMin2
                                     END
      FROM   YourTable
      WHERE  AuditRowId >= @AuditRowIdMin2 + ( ( @AuditRowIdMax2 - @AuditRowIdMin2 ) / 2 )
      ORDER  BY AuditRowId ASC

      IF @AuditRowIdMax2 - @AuditRowIdMin2 <= 1
        SET @AuditRowIdEnd = @AuditRowIdMax2;
  END

SELECT *
FROM   YourTable
WHERE  AuditRowId > @AuditRowIdStart
       AND AuditRowId < @AuditRowIdEnd
ORDER  BY AuditRowId 

答案 1 :(得分:1)

据推测,您可以使用如下查询:

select count(*)
from audi
where auditdtim >= @auditstart and auditdtim <= @auditend;

索引的存在主要影响查询的性能,而不是它的编写方式。

答案 2 :(得分:1)

跟进马丁的回答。尝试使用cte表达式。张贴@ http://sqlfiddle.com/#!6/0270c/9/0

代码:

--declare @SearchTerm datetime = '2000-01-11 00:00:00';
; with cte as
(
  select TOP 1
      Iter=convert(int,0),
      CurDTim=convert(datetime, null),
      CurId=convert(int, -1),
      CurMinId=convert(int,-1),
      CurMaxId=convert(int,-1),
      NextMinId=MIN([AuditRowId]),
      NextMaxId=MAX([AuditRowId])
    from [YourTable] (nolock) --assume clustered index only on AuditRowId
  UNION ALL
  select
      Iter=Iter+1,
      CurDTim=NextDTim,
      CurId=(((NextMaxId+NextMinId)/2)+1),
      CurMinId=NextMinId,
      CurMaxId=NextMaxId,
      NextMinId=case when NextDTim<(/*@SearchTerm*/ '2000-01-11 00:00:00' /*@SearchTerm*/) then (((NextMaxId+NextMinId)/2)+1) else NextMinId end,
      NextMaxId=case when NextDTim>(/*@SearchTerm*/ '2000-01-11 00:00:00' /*@SearchTerm*/) then (((NextMaxId+NextMinId)/2)+1) else NextMaxId end
    from cte t1
    inner join (select AuditRowId, NextDTim=AuditDTim from [YourTable] (nolock)) as t2
      on [AuditRowId] = (((NextMaxId+NextMinId)/2)+1)
    where 
      NextMinId < NextMaxId
      and (((NextMaxId+NextMinId)/2)+1) > NextMinId
      and (((NextMaxId+NextMinId)/2)+1) < NextMaxId
      and (CurDTim <> (/*@SearchTerm*/ '2000-01-11 00:00:00' /*@SearchTerm*/) or CurDTim is null)
)
select TOP 100 t1.*
from cte t1
order by t1.Iter
;