【问题标题】:oracle: tune correlated subqueries in select clauseoracle:调整 select 子句中的相关子查询
【发布时间】:2011-07-12 20:46:01
【问题描述】:

在 Oracle 10gR2 上,给定以下查询,它需要永远运行。这是因为 select 子句中的所有相关子查询。必须有更好的方法。我认为将相关子查询重写为连接可以解决问题,或者以只需要一个相关子查询的方式构建查询,或者可能使用某种类型的分析函数,但到目前为止它超出了我的范围。任何帮助将不胜感激。

这是查询:

SELECT COL_1,
       TAB_1.COL_2                                 AS REPORT,
       (SELECT COL_3
        FROM   TAB_2
        WHERE  TAB_2.COL_1 = TAB_1.COL_1)          AS DEPOT,
       (SELECT COUNT(DISTINCT( TAB_3.COL_4 ))
        FROM   TAB_3
        WHERE  TAB_3.COL_9 = TAB_1.COL_1
               AND TAB_1.COL_2 = TAB_3.COL_6
               AND TAB_3.COL_5 IS NULL
               AND TAB_3.COL_8 = 'Parts Shortage') AS P_SHORTAGES,
       (SELECT COUNT(DISTINCT( Trim(COL_10) ))
        FROM   TAB_3
        WHERE  TAB_3.COL_9 = TAB_1.COL_1
               AND TAB_1.COL_2 = TAB_3.COL_6
               AND TAB_3.COL_5 IS NULL
               AND TAB_3.COL_8 = 'Parts Shortage') AS PARTS_AFFECTED,
       (SELECT COUNT(TAB_3.COL_7)
        FROM   TAB_3
        WHERE  TAB_3.COL_7 = 1
               AND TAB_3.COL_9 = TAB_1.COL_1
               AND TAB_1.COL_2 = TAB_3.COL_6
               AND TAB_3.COL_5 IS NULL
               AND TAB_3.COL_8 = 'Parts Shortage') AS PARTS_CATEGORY1,
       (SELECT COUNT(TAB_3.COL_4)
        FROM   TAB_3
        WHERE  TAB_3.COL_9 = TAB_1.COL_1
               AND TAB_3.COL_6 = TAB_1.COL_2
               AND TAB_3.COL_5 IS NULL
               AND TAB_3.COL_8 = 'Unsrv Asset')    AS U_SHORTAGES,
       (SELECT COUNT(TAB_3.COL_10)
        FROM   TAB_3
        WHERE  TAB_3.COL_9 = TAB_1.COL_1
               AND TAB_3.COL_6 = TAB_1.COL_2
               AND TAB_3.COL_5 IS NULL
               AND TAB_3.COL_8 = 'Unsrv Asset')    AS U_AFFECTED,
       (SELECT COUNT(DISTINCT( Trim(TAB_3.COL_7) ))
        FROM   TAB_3
        WHERE  TAB_3.COL_7 = 1
               AND TAB_3.COL_9 = TAB_1.COL_1
               AND TAB_3.COL_6 = TAB_1.COL_2
               AND TAB_3.COL_5 IS NULL
               AND TAB_3.COL_8 = 'Unsrv Asset')    AS UNSRV_CAT1,
       To_char(TAB_1.COL_11, 'MM/DD/YY')           AS REPORT_DATE
FROM   TAB_1;  

为了让事情变得不那么复杂,我尝试只重写以下内容,但我仍然不知所措:

    SELECT COL_1,
       (SELECT COUNT(DISTINCT( TAB_3.COL_4 ))
        FROM   TAB_3
        WHERE  TAB_3.COL_9 = TAB_1.COL_1
               AND TAB_1.COL_2 = TAB_3.COL_6
               AND TAB_3.COL_5 IS NULL
               AND TAB_3.COL_8 = 'Parts Shortage') AS P_SHORTAGES
FROM   TAB_1;  

【问题讨论】:

  • 在决定是否需要重写查询之前 - 您可能会 - 您是否对查询运行了解释计划?你从中得到了什么?并告诉我们您在表格上的索引。特别是,如果 TAB_3 (COL_9,COL6,COL_8) 上有索引,我会很感兴趣。并检查您是否有关于表格的最新统计数据。
  • 我当然希望这些不是实际的表名和列名。如果是,您可能会考虑寻找一个理智的工作场所。
  • 这很有趣。是的,这些列有更合理的名称。更改名称只是为了稍微抽象一下数据库并保护无辜者。该数据库本质上有点机密。

标签: oracle performance select correlated-subquery


【解决方案1】:

调整相关子查询的最有效方法是在连接列上设置适当的索引。因此,我建议您在连接列上设置适当的索引。

Tab3 上 col9、col6 的复合索引。

Tab1 上 col1 和 col2 的复合索引。

Tab3.col5 上的索引(如果它有最多的空值,否则不创建这个)

tab3.col8 create index <index_name> on tab3(upper(col8))上的基于函数的索引

并更改查询以使用 UPPER 函数 say..UPPER(TAB_3.COL_8) = 'PARTS SHORTAGE'...

记得在执行查询之前执行这个语句,否则基于函数的索引是没有用的。

alter session set query_rewrite_enabled=true(这使得优化器可以使用 FBI)。

然后执行查询。如果查询正确使用所有索引,请检查解释计划...否则添加 /*+Index() */ 提示以通过查询正确使用索引。

【讨论】:

    【解决方案2】:

    这是一个相当繁重的问题。这里没有足够的信息(例如执行计划、记录计数、可用索引、表中数据的物理位置等)能够正确调整查询。任何人都可以做的最好的事情就是猜测。话虽如此..这是我最好的猜测:

    select distinct
        tab_1.col_1,
        tab_1.col_2 AS report,
        tab_2.col_3 AS depot,
        COUNT (DISTINCT (case when tab_3.col_8 = 'Parts Shortage' then tab_3.col_4 end)) over (partition by tab_1.col_1, tab_1.col_2) AS p_shortages,
        COUNT (DISTINCT (case when tab_3.col_8 = 'Parts Shortage' then TRIM (tab_3.col_10) end)) over (partition by tab_1.col_1, tab_1.col_2)  AS parts_affected,
        COUNT (case when tab_3.col_8 = 'Parts Shortage'  and tab_3.col_7 = 1 then tab_3.col_7 end) over (partition by tab_1.col_1, tab_1.col_2)  AS parts_category1,
        COUNT (case when tab_3.col_8 = 'Unsrv Asset' then tab_3.col_4 end) over (partition by tab_1.col_1, tab_1.col_2)  AS u_shortages,
        COUNT (case when tab_3.col_8 = 'Unsrv Asset' then tab_3.col_10 end) over (partition by tab_1.col_1, tab_1.col_2)  AS u_affected,
        COUNT (DISTINCT (case when tab_3.col_8 = 'Unsrv Asset' and tab_3.col_7 = 1 then TRIM(tab_3.col_7) end)) over (partition by tab_1.col_1, tab_1.col_2)  AS unsrv_cat1,
        TO_CHAR(tab_1.col_11, 'MM/DD/YY') AS report_date
    from tab_1
        left outer join tab_2
            on tab_2.col_1 = tab_1.col_1
        left outer join tab_3
            on     tab_3.col_9 = tab_1.col_1
            AND tab_3.col_6 = tab_1.col_2 
            AND tab_3.col_5 IS NULL;
    

    在仔细查看了上面的查询以解释我做了什么之后,我进一步修改了它。这是因为我按 TO_CHAR(tab_1.col_11, 'MM/DD/YY') 进行分组,但看到这不是相关子查询条件的一部分,所以它必须更改(除非 tab_1.col_1 和 col_2 的组合是独一无二的)。

    所以现在试着解释一下:

    基本上,原始查询中发生的情况是,对于 tab_1 的每一行,您都在 tab_3 上运行多个查询。因此,我将其更改为 tab_3 上的外部联接。由于我不知道数据,因此这必须是外部联接,因为相关子查询不会从最终输出中消除任何行,而内部联接可能会在其中消除。我刚刚加入 tab_3 一次,因为所有子查询都使用相同的字段将 tab_3 加入回 tab_1。我只是将每个子查询的特定逻辑移动到 count 内的 case 语句中,这样如果不满足条件,case 将返回 null(因此不被计算在内)。这个最新版本使用聚合函数来让我的计算处于正确的级别(tab_1 col_1 和 col_2 这是原始子查询加入的基础)。由于将子查询转换为外连接可能会创建更多行(如果表之间没有一对一匹配),我添加了 distinct 以仅为 tab_1 中的每一行获取一行。 tab_1 中每一行的所有行都应该相同。如果 tab_1 中已经存在重复项,则您必须做一些更深入的操作以保持记录数相同。

    希望这是有道理的。如果您有任何问题,请随时提出,我会尽力进一步解释。

    ---------------更多解释

    @shawno:有点,但我并不认为它是一个循环。使用您的简化示例,让我们假设您的表格如下所示:

    TAB_1:
    
    col_1           col_2
    --------        ---------
    A               B
    C               D
    
    TAB_3:
    
    col_9           col_6          col_4
    --------        ---------      ---------
    A               B              X
    A               B              Y
    A               B              Z
    C               D              X
    C               D              X
    

    使用子查询方法,您可以查看 tab_1 的每一行,然后针对 tab_3 运行查询。所以你会这样做:

    对于行 col_1 = A, col_2 = B,在 tab_3 上运行 select count(distinct(col_4)),其中 col_9 = A 和 col_6 = B。这将返回值 3,这是子查询返回的值。

    对于行 col_1 = C, col_2 = D,在 tab_3 上运行 select count(distinct(col_4)),其中 col_9 = C 和 col_6 = D。这将返回值 1,这是子查询返回的值。

    使用 join 方法,您首先连接表,为您提供如下数据:

    col_1           col_2          col_9           col_6          col_4
    --------        ---------      --------        ---------      ---------
    A               B              A               B              X
    A               B              A               B              Y
    A               B              A               B              Z
    C               D              C               D              X
    C               D              C               D              X
    

    所以现在您只需根据该数据进行查询,对 col_1、col_2 的每个值执行一次 Count(distinct(col_4))。如果您知道自己的数据是什么样的,就可以创建更高效​​的查询,但思路保持不变。

    希望这能让它更清楚一点!

    【讨论】:

    • 需要注意的是,由于我的猜测包含 group by,如果 group by 条件在 tab_1 中实际上不是唯一的,则返回的记录数可能与您的原始查询不同。
    • 哇,这太棒了!我没有放其他信息——解释计划、记录计数等,因为我真的对“如何摆脱相关子查询以返回结果”感兴趣。查询从 200 多秒缩短到 0.5 秒。但是,我不太清楚为什么会这样。当您有时间逐步了解您所做的以及如何摆脱相关子查询时,您能否花一两分钟时间? --谢谢。
    • 更新了查询并尽力解释。
    • 感谢您的解释。非常有用,非常感谢。新查询稍慢,但似乎产生相同的结果。但是,我真的很喜欢您的方法,并感谢您花时间使用不同的方法重写它。所以要清楚,使用外连接本质上为相关子查询创建的每一行产生相同的for循环?我为子查询“看到”了它,但是围绕使用连接的工作原理来解决我的问题给我带来了问题。然后你对分区语句的使用——受到启发。谢谢。
    • 很好的解释。再次感谢您抽出时间来做这件事。它确实帮助我理解了这个概念。
    猜你喜欢
    • 2015-12-19
    • 2019-09-15
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 2014-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多