oracle:在select子句中调整相关子查询

时间:2011-03-10 16:21:28

标签: oracle performance select correlated-subquery

在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;  

2 个答案:

答案 0 :(得分:3)

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

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。我只是将每个子查询的特定逻辑移动到计数内的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上运行选择计数(distinct(col_4)),其中col_9 = A且col_6 = B.这将返回值3,即子查询返回的值。

对于行col_1 = C,col_2 = D,在tab_3上运行选择计数(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))。如果你知道你的数据是什么样的,你可以创建一个更有效的查询,但这个想法保持不变。

希望这会让它更清晰一点!

答案 1 :(得分:2)

调整核心子查询的最有效方法是在连接列时使用适当的索引。因此,我建议您在连接列上使用适当的索引。

Tab3上col9,col6的复合索引。

Tab1上col1和col2的复合索引。

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

tab3.col8 create index <index_name> on tab3(upper(col8))

上基于函数的索引

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

请记住在执行查询之前执行此语句,否则基于函数的索引无效。

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

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