【问题标题】:Finding head and tail events in SQL Server (Optimisation)在 SQL Server 中查找头尾事件(优化)
【发布时间】:2010-09-22 03:05:11
【问题描述】:

我有一个事件表,我需要找到类型 1 的所有尾部事件和类型 1 的所有头部事件。

因此,对于按 [1, 1], 3, 1 ,4, 5, [1,1,1] 顺序排列的事件集,括号表示类型 1 的头事件和尾事件。

这在 SQL 中得到了更好的说明:

drop table #event
go 
create table #event (group_id int, [date] datetime, [type] int)
create index idx1 on #event (group_id, date)


insert into #event values (1, '2000-01-01', 1) 
insert into #event values (1, '2000-01-02', 1) 
insert into #event values (1, '2000-01-03', 3) 
insert into #event values (1, '2000-01-04', 2) 
insert into #event values (1, '2000-01-05', 1) 
insert into #event values (2, '2000-01-01', 2) 
insert into #event values (2, '2000-01-02', 2) 
insert into #event values (2, '2000-01-03', 3) 
insert into #event values (2, '2000-01-04', 2) 
insert into #event values (2, '2000-01-05', 1) 
insert into #event values (3, '2000-01-01', 1) 
insert into #event values (3, '2000-01-02', 2) 
insert into #event values (3, '2000-01-03', 1) 
insert into #event values (3, '2000-01-04', 2) 
insert into #event values (3, '2000-01-05', 2) 
insert into #event values (4, '2000-01-01', 2) 
insert into #event values (4, '2000-01-02', 2) 
insert into #event values (4, '2000-01-03', 3) 
insert into #event values (4, '2000-01-04', 1) 
insert into #event values (4, '2000-01-05', 1) 

go 

select e1.* from #event e1 
where (
    not exists (
        select top 1 1 
        from #event e2 
        where e1.group_id = e2.group_id 
        and e2.date < e1.date 
        and e2.type <> 1
    ) or not exists (
        select top 1 1 
        from #event e2 
        where e1.group_id = e2.group_id 
        and e2.date > e1.date 
        and e2.type <> 1
    ) 
)
and e1.type = 1 

预期结果:

1   2000-01-01 00:00:00.000 1
1   2000-01-02 00:00:00.000 1
1   2000-01-05 00:00:00.000 1
2   2000-01-05 00:00:00.000 1
3   2000-01-01 00:00:00.000 1
4   2000-01-04 00:00:00.000 1
4   2000-01-05 00:00:00.000 1

这一切都很好,并返回了我的预期结果,但它扫描了表格 3 次。有什么办法可以让这个执行得更快并减少表扫描的次数?

【问题讨论】:

  • 完成,这是 sql 2005 但我也需要支持 2000

标签: sql-server optimization


【解决方案1】:

要生成大量数据子集,您可以使用:

declare @i int 
set @i = 10000
while @i > 5 
begin
    insert into #event values (@i, '2000-01-01', 1) 
    insert into #event values (@i, '2000-01-02', 1) 
    insert into #event values (@i, '2000-01-03', 3) 
    insert into #event values (@i, '2000-01-04', 2) 
    insert into #event values (@i, '2000-01-05', 1)  
    set @i = @i -1 
end 

此外,要在每个组中包含大量事件,请尝试以下操作:

declare @j int 
set @j = 0 
while @j < 10
begin 
    set nocount on 
    declare @i int 
    set @i = 0
    while @i < 10000 
    begin
        insert into #event values (@j, DateAdd(d, @i, '2000-01-01'), rand(10) * 10) 

        set @i = @i  +1 
    end
    set @j = @j + 1 
end
set nocount off

在我所有的测试中,我的原始查询似乎只产生了 3 次表扫描,我不确定是否可以在这里提高性能。

【讨论】:

    【解决方案2】:

    我认为这样更好:

    select e1.group_id, e1.date, e1.type<br>
    from #event e1, #event e2<br>
    where e1.type = 1<br>
    and e2.type <> 1<br>
    and e1.group_id= e2.group_id<br>
    group by e1.group_id, e1.date, e1.type, e2.group_id<br>
    having e1.date < min(e2.date) or e1.date > max(e2.date)
    

    【讨论】:

    • 虽然这并没有达到理想的一次表扫描,但它确实将表扫描减少到2,并大大简化了执行计划。 +1
    • 最初我认为这会更快,但如果同一组有很多活动,它就会崩溃
    【解决方案3】:

    考虑

    在#event(类型)上创建索引 idx2

    我没有要检查的 SQL Server,但在 Oracle 中它会消除顶级扫描(对于 'type=1' 条件)。

    关于查询本身——在 MS SQL 2000 中,'[not] exists' 和 '[not] in' 谓词几乎总是进行全扫描——我们用适当的 JOIN 替换它们。

    【讨论】:

    • 另一个索引在这里没有帮助,同样的性能,3 次表扫描。我想问题的一部分是“有什么办法可以用连接代替它”
    【解决方案4】:

    据我了解,您所追求的是头部和尾部,按天排序**,每个 ID**。头部和尾部是所有记录,直到遇到类型不是一个的记录。

    这是一种不同的方式,它可能会更快

    ;WITH Ranked AS (
        SELECT 
            *,
            Row_Number() OVER (ORDER BY date) as 'rnk'
        FROM #event
    )
    
    
    SELECT * 
    FROM Ranked
    WHERE rnk not between 
            (SELECT Min(rnk) FROM Ranked r WHERE r.type <> 1 AND ranked.id = r.id)
            AND (SELECT Max(rnk) FROM Ranked r WHERE r.type <> 1 AND ranked.id = r.id)
    order by id
    

    您不需要将 TOP 与 exists 语句一起使用,尽管它没有坏处。

    【讨论】:

    • 根据 sql 跟踪,这需要两倍的读取次数和 20% 的时间,根据查询执行计划,它慢了 30 倍(但计划中的数字通常太高了)跨度>
    【解决方案5】:

    查询优化器只有 20 条记录,很容易得出结论,无论您如何表达查询或创建什么索引,3 次表扫描都是最快的方法。对于小数据记录,整个表可能会加载一次或少量磁盘读取;并且在读取块中没有涉及太多优化;优化器主要对最小化磁盘活动感兴趣,其他一切都相对无关紧要。优化器对这么少的记录所做的事情并不能很好地表明它将如何处理更大的数据量。

    你有一个包含更多数据的真实表格吗?我无法想象你能感知到任何执行时间。

    【讨论】:

    • 公平点将修改问题以包含大量数据集
    • 好吧,仍然可以使用 Roberts Query 进行 3 次表扫描,但返回结果花费了太长时间
    • 顺便说一句,我同时使用 SQL Server Profiler 并查看执行计划以确定性能
    【解决方案6】:

    这是一个带有连接的版本:

    select distinct e1.* from #event e1 
    left outer join #event e2 ON 
            e1.id = e2.id 
            and e2.date < e1.date 
            and e2.type <> 1
    left outer join #event e3 ON
            e1.id = e3.id 
            and e3.date > e1.date 
            and e3.type <> 1
    where e1.type = 1 AND (e2.id is null or e3.id is null)
    

    这仍然有三个表扫描和一个 distinct 子句,但它似乎仍然比原始查询快。

    【讨论】:

      【解决方案7】:

      基本上看起来您正在寻找类型 1 的事件,该事件小于时间戳中的其他事件并且大于其他日期的时间戳事件 试试这个,它是用 Oracle 语法编写的,不确定 MSSQL。

      select e1.* from e1 where 
      e1.id = 1 and (e1.date <=
      (
      select min(e2.date) from e2 where
      e2.id <> 1
      group by e2.date
      )
      or 
      (e1.date >= 
      select max(e3.date) from e3 where
      e3.id <> 1
      group by e3.date
      )
      )
      

      【讨论】:

      • 我无法让这个 SQL 在 SQL Server 中工作......即使我正确地为所有内容设置别名,子查询也会产生错误
      【解决方案8】:

      这是一个查询,其显示计划中至少没有说“表扫描”,并给出了相同的答案。

      不过,我还没有从逻辑上理解这个查询。

      SELECT DISTINCT e1.* FROM #event e1  
      WHERE e1.type = 1  
          AND   
          (  
              NOT EXISTS (  
                  SELECT 1 FROM #event  
                  WHERE type != 1  
                      AND id = e1.id   
                      AND date < e1.date  
              )  
              OR NOT EXISTS (  
                  SELECT 1 FROM #event  
                  WHERE type != 1  
                      AND id = e1.id   
                      AND date > e1.date  
              )  
          )  
      

      【讨论】:

      • 不,坚持 - 我在应用您的数据生成器后进行了测试。但不要让步。
      【解决方案9】:

      这个比上一个好:

      SELECT e1.* FROM event e1  
      WHERE e1.type = 1  
          AND NOT EXISTS 
          (  
              SELECT 1 FROM event  
              WHERE type != 1  
                  AND id = e1.id   
                  AND date < e1.date  
          )  
      UNION ALL
      SELECT e1.* FROM event e1  
      WHERE e1.type = 1  
          AND NOT EXISTS 
          (  
              SELECT 1 FROM event  
              WHERE type != 1  
                  AND id = e1.id   
                  AND date > e1.date  
          )  
      

      它得到相同的结果。但是你的查询,以及我之前的查询,都很糟糕。这是 IO 统计数据:

      (29985 行受影响)

      表“事件”。扫描计数 9997,逻辑读取 20477,物理读取 0,预读读取 0。
      表“工作台”。扫描计数 39979,逻辑读取 99950,物理读取 0,预读读取 0。

      最近的查询也是这样:

      (29985 行受影响)

      表“事件”。扫描计数 4,逻辑读取 652,物理读取 0,预读读取 0。

      需要注意的两件事--

      1. 即使在最坏的情况下,整个表也会被加载并且没有物理读取。
      2. 坏的有 9997 + 39979 表扫描。不只是 4 个。

      请描述查询的意图。

      【讨论】:

        猜你喜欢
        • 2013-08-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-04
        • 2018-09-30
        相关资源
        最近更新 更多