【问题标题】:Alternate to 'Except' in SQL with performance以性能替代 SQL 中的“除外”
【发布时间】:2017-03-12 08:58:13
【问题描述】:

我在 MS-Sql 中有一个表 TableA

 TrId   Status
 2345   3
  567   3
  567   0
 2345   0
   99   3
  778   0

场景是少数 TrId 的状态为 3 和 0 ,有些只有 3 ,有些只有 0 。我只需要找到状态为 3 的 TrId。
一种方法是:

Select TrnId From TableA Where flgStatus = 3
EXCEPT
Select TrnId From Tablea Where flgStatus = 0

有超过 1 亿条记录,我没有足够的时间窗口,除非,任何替代方案都会很感激。

【问题讨论】:

标签: sql sql-server sql-except


【解决方案1】:

您可以使用NOT EXISTS

SELECT *
FROM TableA a
WHERE flgStatus = 3
AND NOT EXISTS 
(SELECT TrnId From TableA b Where flgStatus = 0 AND a.TrnId = b.TrnId) 

这通常比NOT IN 具有更好的性能。一个好的替代方案是加入,请参阅@ThomasG 的回答。

【讨论】:

    【解决方案2】:

    (TrnId,flgStatus) 的组合是唯一的吗?

    然后你可能会切换到EXCEPT ALL,类似于UNION ALL,它可能比UNION 更有效,因为它避免了DISTINCT 操作。

    另一种只访问基表一次的解决方案:

    Select TrnId 
    From TableA Where flgStatus in (0,3)
    group by TrnId
    having MIN(flgStatus) = 3
    

    【讨论】:

    • 这不会产生预期的结果。如果您将其更改为MIN(flgStatus) = 3,尽管它确实如此。
    【解决方案3】:

    EXCEPTMINUS 在这里是正确的。然而,在一张非常大的桌子上,它并不是最佳选择。

    另一种选择是这样

    SELECT *
    FROM TableA 
    WHERE flgStatus = 3
    AND TrnId  NOT IN
    (SELECT TrnId From TableA Where flgStatus = 0) 
    

    或者甚至更好,使用LEFT JOINIS NULL 来避免NOT 这是一个性能杀手:

    SELECT *
    FROM TableA T3
    LEFT JOIN TableA T0 ON T3.TrnId = T0.TrnId AND T0.flgStatus = 0
    WHERE T3.flgStatus = 3
      AND T0.TrnId IS NULL
    

    编辑:来自 Igor 的 NOT EXISTS 解决方案也是一个好方法

    【讨论】:

    • 我会在星期二检查这方面的表现,如果我必须通过绿色检查,请告诉您! :)
    【解决方案4】:

    我会使用一个简单的group by

    select trnid
    from tablea
    group by trnid
    having min(status) = max(status) and min(status) = 3;
    

    这是否更快取决于几件事。 . .尤其是您是否要删除重复项以及您对数据有哪些索引。 NOT EXISTS 如果您不关心重复,可能会更快,但消除重复需要工作。

    【讨论】:

    • 嗯,这几乎是@dnoeth 回答时间的两倍。
    • @EetSandhu 。 . .那么你的表中必须有其他状态。
    • 是的。 0,1,3 所有所有状态类型。我在上面评论只是为了大家。否则它工作正常。
    【解决方案5】:

    对于像您这样的大型数据集,使用以下查询可能会以合理的性能提供您想要的结果 -

    SELECT ta1.TrId AS TrId
    FROM dbo.TableA AS ta1
    LEFT JOIN dbo.TableA AS ta2 ON (ta2.TrId = ta1.TrId AND ta2.[Status] != 3)
    WHERE ta2.TrId IS NULL;
    

    首先,自连接通过将所有状态(3 或 0、1 等)排列在同一行中来创建一个表。过滤器

     ta2.[Status] != 3
    

    如果 Status 为 3,则在 join 子句中为 ta2.TrId(或 ta2.*)设置 NULL。

    +------+--------+------+--------+
    | TrId | Status | TrId | Status |
    +------+--------+------+--------+
    | 2345 |      3 | 2345 | 0      |
    |  567 |      3 | 567  | 0      |
    |  567 |      0 | 567  | 0      |
    | 2345 |      0 | 2345 | 0      |
    |   99 |      3 | NULL | NULL   |
    |  778 |      0 | 778  | 0      |
    +------+--------+------+--------+
    

    然后使用以下过滤器选择出现 NULL 的行。

    WHERE ta2.TrId IS NULL
    

    由于是self LEFT JOIN,所以左表有所有行,但右表值不满足连接条件的为NULL。

    【讨论】:

    • 我会在星期五检查这个的性能并告诉你。如果这对我或@dnoeth 的接受答案最有效。
    • 我之前不知道您的状态不是 0 和 3。所以我更新了我的答案并稍微更改了 join 子句的过滤器。我也很想看看这个解决方案的 group by 和 join 版本对于巨大数据集的比较。
    猜你喜欢
    • 2020-09-10
    • 2022-01-22
    • 2019-11-23
    • 2011-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多