【问题标题】:Rewriting Correlated Subquery with "not in" clause用“not in”子句重写相关子查询
【发布时间】:2013-12-18 02:22:20
【问题描述】:

出于明显的性能原因,我想重写一个现有的 Oracle SQL 查询,其中包括涉及“not in”子句的相关子查询。这可以通过外部连接或其他一些技术来实现吗?

代码如下:

SELECT TRIM(et.event_id), TRIM(et.cancel_evt_id)
FROM external_transactions et
JOIN transaction_type tt
    ON et.transaction_type_id = tt.transaction_type_id
WHERE et.acct = 'ABCDEF' 
AND tt.transaction_type_class != 'XYZXYZ'
AND 
(
    TRIM(et.event_id) NOT IN
        (
        SELECT TRIM(t1.transaction_evt_id)
        FROM transactions t1
        WHERE t1.acct = et.acct
        AND t1.asset_id = et.asset_id
        AND t1.cancel_flag = 'N' 
        )
    OR TRIM(et.cancel_evt_id) NOT IN
        (
        SELECT TRIM(t2.cancel_evt_id)
        FROM transactions t2
        WHERE t2.acct = et.acct
        AND t2.asset_id = et.asset_id
        AND t2.cancel_flag = 'Y'
        )
)
;

【问题讨论】:

  • “明显的原因”是什么?我怀疑在 id 上使用 trim() 会使查询的效率低于其他情况。
  • 发布你的执行计划。
  • 进行左连接并应用列的 WHERE IS NULL 通常要快得多。然而,话虽这么说......“et.event_id”和“t1.transaction_evt_id”的实际数据类型是什么。如果是数字,执行 TRIM() 是不好的,正如 Gordon 所说的那样。
  • 在等上,列是 varchar2(55)。在 t 上,列是 varchar2(255)。这些模式可能是在不同时间创建的。为了快速取胜,我将尝试删除 trim() 语句,看看它们是否在逻辑上是必要的。但我怀疑我需要更显着地重组查询。表/字段名称已重命名以保护我客户的数据,因此我将无法发布解释计划。
  • @DRapp 我没有遇到与相关子查询相关的性能问题。如果 Oracle CBO 认为这会产生更好的性能计划,它可能会选择将关联子查询重写为底层连接。请参阅此Ask Tom 回复。我说写查询,这样它就清楚了,让 CBO 做这件事很神奇。

标签: sql oracle correlated-subquery


【解决方案1】:

除了注释之外,这是假设您的“ID”列是基于整数而不是字符串的,不要尝试转换它们。

另外,为了帮助优化查询,我会确保您有索引

External_Transactions    ( acct, event_id, cancel_evt_id )
Transaction_Type   ( transaction_type_id, transaction_type_class )
Transactions ( transaction_evt_id, acct, asset_id, cancel_flag )
Transactions ( cancel_evt_id, acct, asset_id, cancel_flag )


SELECT 
      et.event_id, 
      et.cancel_evt_id
   FROM 
      external_transactions et
         JOIN transaction_type tt
            ON et.transaction_type_id = tt.transaction_type_id
           AND tt.transaction_type_class != 'XYZXYZ'
         LEFT OUTER JOIN transactions t1
            ON  et.event_id = t1.transaction_evt_id
            AND et.acct = t1.acct
            AND et.asset_id = t1.asset_id
            AND t1.cancel_flag = 'N'
         LEFT OUTER JOIN transactions t2
            ON  et.cancel_evt_id = t2.cancel_evt_id
            AND et.acct = t2.acct
            AND et.asset_id = t2.asset_id
            AND t2.cancel_flag = 'Y'
   WHERE 
          et.acct = 'ABCDEF' 
      AND (    t1.transaction_evt_id IS NULL
            OR t2.cancel_evt_id IS NULL )

如果事务表有一个索引,您甚至可能会稍微受益

Transactions ( acct, asset_id, cancel_flag, transaction_evt_id, cancel_evt_id )

左连接就像

SELECT 
      et.event_id, 
      et.cancel_evt_id
   FROM 
      external_transactions et
         JOIN transaction_type tt
            ON et.transaction_type_id = tt.transaction_type_id
           AND tt.transaction_type_class != 'XYZXYZ'
         LEFT OUTER JOIN transactions t1
            ON  et.acct = t1.acct
            AND et.asset_id = t1.asset_id
            AND ( 
                  (   t1.cancel_flag = 'N'
                  AND et.event_id = t1.transaction_evt_id )
                OR
                  (   t1.cancel_flag = 'Y'
                  AND et.cancel_event_id = t1.cancel_evt_id )
                )
   WHERE 
          et.acct = 'ABCDEF' 
      AND t1.transaction_evt_id IS NULL

在这两种情况下,索引都是 COVERING 索引,因此不必返回原始数据页面来确认记录的其他元素

【讨论】:

  • DRapp,您的第一个解决方案似乎可以解决问题。 Oracle 解释计划成本估算从 315430 减少到 8715。我仍在测试以确保新查询返回正确的结果,但我还没有找到破坏它的测试条件。
  • 我还将根据几个人的建议调查 trim() 函数在逻辑上是否必要。
  • @ptc3,很高兴它起作用了......但只是想知道之前/之后的整体性能改进......
【解决方案2】:

使用NOT EXISTS

SELECT TRIM(et.event_id), TRIM(et.cancel_evt_id)
FROM external_transactions et
JOIN transaction_type tt
    ON et.transaction_type_id = tt.transaction_type_id
WHERE et.acct = 'ABCDEF' 
AND tt.transaction_type_class != 'XYZXYZ'
AND NOT EXISTS
        (
        SELECT 1
        FROM transactions t1
        WHERE t1.acct = et.acct
        AND t1.asset_id = et.asset_id
        AND ( (   t1.cancel_flag = 'N'
              AND TRIM(et.event_id) = TRIM(t1.transaction_evt_id) )
            OR
              (   t1.cancel_flag = 'Y'
              AND TRIM(et.cancel_evt_id) = TRIM(t1.cancel_evt_id) )
            )
        );

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-31
  • 2012-05-22
  • 2023-03-20
相关资源
最近更新 更多