使用 WHERE NOT IN 子选择子句提高性能

jca*_*314 4 performance execution-plan subquery firebird query-performance

在以下查询中,我必须为每个客户计算交易。但是,我必须从结果集中完全排除交易时间超过一年的客户。

查询优化器不应该足够聪明,只为每个客户评估一次存在吗?

--Count transactions on customers that are less than 1 year old

SELECT t1.CUSTID, COUNT(*)
FROM CUST_TRX t1
WHERE NOT EXISTS ( 
  SELECT FIRST 1 1 
  FROM CUST_TRX t2 
  WHERE 
    t2.CUSTID = t1.CUSTID AND
    t2.DATED < CURRENT_DATE - 365
  GROUP BY t2.CUSTID
)
GROUP BY t1.CUSTID
Run Code Online (Sandbox Code Playgroud)

我的查询计划中没有自然。此查询的执行就像数据库为每个事务运行存在子句,而不是为每个客户运行它。如果我删除GROUP BY子查询中的,则性能相同。

有没有更好的方法来做到这一点,以便我可以从数据库中获得更好的性能?SELECT如果可能的话,希望一个简单的查询能够避免 CTE(这会带来其他挑战)。

由于其他GROUP BY条件(此处未显示),我无法简单地检查MIN(DATED),我确实需要执行另一个查询。

Dav*_*ett 5

对于这样的查询,执行LEFT OUTER JOIN而不是NOT EXISTS样式检查通常更有效,它通常意味着完整的索引扫描(或没有正确索引的表扫描),但主表中有很多行,这是较少的比大量索引查找(从主表返回的每一行在引用表上查找一个)昂贵,否则会导致. 一些查询规划器非常善于发现这种等价性,并在它是更好的选择时使用替代计划,但在您的情况下听起来并没有发生这种情况。

尝试类似:

SELECT t1.CUSTID, COUNT(*)
FROM   CUST_TRX t1
LEFT OUTER JOIN
       CUST_TRX t2 
ON     t2.CUSTID=t1.CUSTID 
AND    t2.DATED<CURRENT_DATE-365
WHERE  t2.CUSTID IS NULL
GROUP BY t1.CUSTID
Run Code Online (Sandbox Code Playgroud)

(注意:我不熟悉 firebird,所以上面的语法可能需要调整,但应该说明这一点)

没有匹配项的WHERE t2.CUSTID IS NULL每一行将t1t2每个匹配项中输出一次t2,没有匹配项的t2将输出一次,但从该对象中选择的任何列都设置为 NULL。WHERE然后该子句筛选出匹配项。

取决于DB发动机的能力,特别是如果数据中的参考对象(该量CUST_TRX与施加在这里过滤器)是巨大的,这可能是显著比效率WHERE <something> NOT INWHERE NOT EXISTS选项,因此基准在真实数据组第一使用该方法之前。在查询计划器没有注意到WHERE NOT IN可以更有效地执行这种安排的情况下,使用 MS SQL Server通常会更有效。

此外,如果您这样做,请在代码(和/或支持文档)中留下评论,说明您正在这样做,WHERE <something> NOT IN或者WHERE NOT EXISTS您希望这样做更有效。您会记住它,并且有经验的 SQL 人员会识别出该模式,但是查看代码的其他人可能不会立即理解其意图/原因并将其翻转回使用WHERE NOT EXISTS以确保清晰,因为这样读起来更像英文句子。