在不使用相关子查询的情况下重写查询

Posted

技术标签:

【中文标题】在不使用相关子查询的情况下重写查询【英文标题】:Rewrite Query without using correlated subqueries 【发布时间】:2011-11-21 20:54:26 【问题描述】:

在 LINUX 上使用 Oracle 10gR2,我正在尝试调整以下查询。 我很确定摆脱相关子查询和可能使用一些分析函数可能是最佳方法,但我就是不明白——尤其是在 MAX 上选择的嵌套相关子查询( TABLE_2.NOTE_DATE)。任何帮助将非常感激。谢谢。

EXPLAIN PLAN FOR
SELECT TABLE_4.INCIDENT_TYPE, 
   TABLE_4.POC_CONTACT, 
   (SELECT TABLE_2.NOTE_DATE 
           || ' ' 
           || TABLE_1.USER_FIRST_NAME 
           || ' ' 
           || TABLE_1.USER_LAST_NAME 
           || ' : ' 
           || TABLE_2.OTHER_HELP_NOTES 
    FROM   TABLE_1, 
           TABLE_2 
    WHERE  TABLE_2.USER_ID = TABLE_1.USER_ID 
           AND TABLE_2.REC_ID = TABLE_4.REC_ID 
           AND TABLE_2.NOTE_DATE = (SELECT MAX(TABLE_2.NOTE_DATE) 
                                              FROM   TABLE_2 
                                              WHERE  TABLE_2.REC_ID = TABLE_4.REC_ID 
                                                     AND TABLE_2.NOTE_DATE <= 
                                                         TABLE_4.REPORT_DATE)) 
                                                                 AS SUM_OF_SHORTAGE, 
   (SELECT TABLE_3.NOTE_DATE 
           || ' ' 
           || TABLE_1.USER_FIRST_NAME 
           || ' ' 
           || TABLE_1.USER_LAST_NAME 
           || ' : ' 
           || TABLE_3.HELP_NOTES 
    FROM   TABLE_1, 
           TABLE_3 
    WHERE  TABLE_3.USER_ID = TABLE_1.USER_ID 
           AND TABLE_3.REC_ID = TABLE_4.REC_ID 
           AND TABLE_3.NOTE_DATE = (SELECT MAX(TABLE_3.NOTE_DATE) 
                                                  FROM   TABLE_3 
                                                  WHERE  TABLE_3.REC_ID = TABLE_4.REC_ID 
                                                         AND TABLE_3.NOTE_DATE <= 
                                                             TABLE_4.REPORT_DATE)) AS HELP_NOTES, 
   TABLE_4.REPORT_NUM 
FROM   TABLE_4 
WHERE  TABLE_4.SITE_ID = '1';

@C:\ORACLE\PRODUCT\11.2.0\CLIENT_1\RDBMS\ADMIN\UTLXPLS.SQL;

PLAN_TABLE_OUTPUT                                                                                                                                                                                                                                                                                            
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 
PLAN HASH VALUE: 4036328474                                                                                                                                                                                                                                                                                  

------------------------------------------------------------------------------------------------------------                                                                                                                                                                                                 
| ID  | OPERATION                     | NAME                       | ROWS  | BYTES | COST (%CPU)| TIME     |                                                                                                                                                                                                 
------------------------------------------------------------------------------------------------------------                                                                                                                                                                                                 
|   0 | SELECT STATEMENT              |                            | 13009 |  2286K|   449   (2)| 00:00:06 |                                                                                                                                                                                                 
|*  1 |  FILTER                       |                            |       |       |            |          |                                                                                                                                                                                                 
|   2 |   NESTED LOOPS                |                            |     3 |   612 |     8   (0)| 00:00:01 |                                                                                                                                                                                                 
|   3 |    TABLE ACCESS BY INDEX ROWID| TABLE_2                    |     3 |   552 |     5   (0)| 00:00:01 |                                                                                                                                                                                                 
|*  4 |     INDEX RANGE SCAN          | IX_TABLE_2_REC_ID          |     3 |       |     1   (0)| 00:00:01 |                                                                                                                                                                                                 
|   5 |    TABLE ACCESS BY INDEX ROWID| TABLE_1                    |     1 |    20 |     1   (0)| 00:00:01 |                                                                                                                                                                                                 
|*  6 |     INDEX UNIQUE SCAN         | TABLE_1_PK                 |     1 |       |     0   (0)| 00:00:01 |                                                                                                                                                                                                 
|   7 |   SORT AGGREGATE              |                            |     1 |    13 |            |          |                                                                                                                                                                                                 
|*  8 |    TABLE ACCESS BY INDEX ROWID| TABLE_2                    |     1 |    13 |     5   (0)| 00:00:01 |                                                                                                                                                                                                 
|*  9 |     INDEX RANGE SCAN          | IX_TABLE_2_REC_ID          |     3 |       |     1   (0)| 00:00:01 |                                                                                                                                                                                                 
|* 10 |  FILTER                       |                            |       |       |            |          |                                                                                                                                                                                                 
|* 11 |   HASH JOIN                   |                            |    17 |  4063 |   482   (2)| 00:00:06 |                                                                                                                                                                                                 
|* 12 |    TABLE ACCESS FULL          | TABLE_3                    |    17 |  3723 |   474   (2)| 00:00:06 |                                                                                                                                                                                                 
|  13 |    TABLE ACCESS FULL          | TABLE_1                    |  1504 | 30080 |     8   (0)| 00:00:01 |                                                                                                                                                                                                 
|  14 |   SORT AGGREGATE              |                            |     1 |    13 |            |          |                                                                                                                                                                                                 
|* 15 |    TABLE ACCESS FULL          | TABLE_3                    |     1 |    13 |   474   (2)| 00:00:06 |                                                                                                                                                                                                 
|* 16 |  TABLE ACCESS FULL            | TABLE_4                    | 13009 |  2286K|   449   (2)| 00:00:06 |                                                                                                                                                                                                 
------------------------------------------------------------------------------------------------------------                                                                                                                                                                                                 

PREDICATE INFORMATION (IDENTIFIED BY OPERATION ID):                                                                                                                                                                                                                                                          
---------------------------------------------------                                                                                                                                                                                                                                                          

   1 - FILTER("TABLE_2"."NOTE_DATE"= (SELECT /*+ */ MAX("TABLE_2"."NOTE_DATE")                                                                                                                                                                                                           
              FROM "TABLE_2" "TABLE_2" WHERE "TABLE_2"."REC_ID"=:B1 AND                                                                                                                                                                                                        
              "TABLE_2"."NOTE_DATE"<=:B2))                                                                                                                                                                                                                                                         
   4 - ACCESS("TABLE_2"."REC_ID"=:B1)                                                                                                                                                                                                                                                              
   6 - ACCESS("TABLE_2"."USER_ID"="TABLE_1"."USER_ID")                                                                                                                                                                                                                                           
   8 - FILTER("TABLE_2"."NOTE_DATE"<=:B1)                                                                                                                                                                                                                                                          
   9 - ACCESS("TABLE_2"."REC_ID"=:B1)                                                                                                                                                                                                                                                              
  10 - FILTER("TABLE_3"."NOTE_DATE"= (SELECT /*+ */                                                                                                                                                                                                                                            
              MAX("TABLE_3"."NOTE_DATE") FROM "TABLE_3" "TABLE_3" WHERE                                                                                                                                                                                            
              "TABLE_3"."REC_ID"=:B1 AND "TABLE_3"."NOTE_DATE"<=:B2))                                                                                                                                                                                                            
  11 - ACCESS("TABLE_3"."USER_ID"="TABLE_1"."USER_ID")                                                                                                                                                                                                                                       
  12 - FILTER("TABLE_3"."REC_ID"=:B1)                                                                                                                                                                                                                                                          
  15 - FILTER("TABLE_3"."REC_ID"=:B1 AND "TABLE_3"."NOTE_DATE"<=:B2)                                                                                                                                                                                                             
  16 - FILTER("TABLE_4"."SITE_ID"=1)                                                                                                                                                                                                                                                            
 41 ROWS SELECTED 

分解这个查询——关键问题似乎如下:

select REC_ID, TO_CHAR(REPORT_DATE,'DD-MON-YY HH:MI:SS') REPORT_DATE,  
(SELECT MAX(TABLE_2.note_date) as MAX_DATE
FROM   TABLE_2 
where  TABLE_2.REC_ID = TABLE_1.REC_ID 
       and TABLE_2.NOTE_DATE <= TABLE_1.REPORT_DATE
) NOTES_MAX_DATE
from TABLE_1 where REC_ID = 121 order by TO_DATE(REPORT_DATE,'DD-MON-YY HH:MI:SS');

应该返回以下内容:

    REC_ID                 REPORT_DATE        NOTES_MAX_DATE                          
---------------------- ------------------ ------------------------- 
121                    17-APR-10 12:30:00                           
121                    24-APR-10 12:30:00                           
121                    01-MAY-10 12:30:00                           
121                    08-MAY-10 12:30:00                           
121                    15-MAY-10 12:30:00 12-MAY-10                 
121                    22-MAY-10 12:30:01 17-MAY-10                 
121                    29-MAY-10 12:30:01 25-MAY-10                 
121                    05-JUN-10 12:30:00 25-MAY-10                 
 8 rows selected 

输出需要和上面的一样。我尝试按如下方式创建连接:

SELECT TABLE_1.REC_ID, TO_CHAR(TABLE_1.REPORT_DATE,'DD-MON-YY HH:MI:SS') REPORT_DATE, MAX(TABLE_2.NOTE_DATE) AS NOTES_MAX_DATE
     FROM   TABLE_2, 
            TABLE_1 
     where  TABLE_2.REC_ID = TABLE_1.REC_ID
            AND TABLE_2.NOTE_DATE <= TABLE_1.REPORT_DATE 
            and ( TABLE_1.SITE_ID = '1' ) 
            and TABLE_1.REC_ID = 121
     group  by TABLE_1.REC_ID, TABLE_1.REPORT_DATE
     order by TO_DATE(REPORT_DATE,'DD-MON-YY HH:MI:SS');

但这会产生:

    REC_ID                 REPORT_DATE        NOTES_MAX_DATE        
---------------------- ------------------ ------------------------- 
121                    15-MAY-10 12:30:00 12-MAY-10                 
121                    22-MAY-10 12:30:01 17-MAY-10                 
121                    29-MAY-10 12:30:01 25-MAY-10                 
121                    05-JUN-10 12:30:00 25-MAY-10                 

所以我真的很难过。有任何想法吗? -- 谢谢。

【问题讨论】:

【参考方案1】:

下面是一个版本,只获取一次max 应该删除相关的子查询。它仍然使用子查询,但是由于它们位于FROM 子句而不是SELECT 子句中,因此数据库应该更好地解决它们。可能也可以删除这些子查询,但这种方式更具可读性。此版本还使用 SQL-99 语法进行连接,这通常被认为是可取的。

SELECT table_4.incident_type, 
       table_4.poc_contact, 
       t2.sum_of_shortage, 
       t3.help_notes, 
       table_4.report_num
FROM             table_4
       LEFT JOIN (SELECT table_2.rec_id,
                         table_2.note_date
                         || ' '
                         || table_1.user_first_name
                         || ' '
                         || table_1.user_last_name
                         || ' : '
                         || table_2.other_help_notes
                            AS sum_of_shortage
                  FROM   table_1 
                    JOIN table_2 
                      ON table_2.user_id = table_1.user_id
                  WHERE  table_2.note_date =
                            (SELECT MAX(table_2.note_date) AS max_date
                             FROM   table_2
                             WHERE  table_2.rec_id = table_4.rec_id 
                                AND table_2.note_date <= table_4.report_date)) t2
              ON t2.rec_id = table_4.rec_id
       LEFT JOIN (SELECT table_3.rec_id,
                         table_3.note_date
                         || ' '
                         || table_1.user_first_name
                         || ' '
                         || table_1.user_last_name
                         || ' : '
                         || table_3.other_help_notes
                            AS help_notes
                  FROM   table_1 
                    JOIN table_3 
                      ON table_3.user_id = table_1.user_id
                  WHERE  table_2.note_date =
                            (SELECT MAX(table_3.note_date) AS max_date
                             FROM   table_3
                             WHERE  table_3.rec_id = table_4.rec_id 
                                AND table_3.note_date <= table_4.report_date)) t3
              ON t3.rec_id = table_4.rec_id
WHERE  table_4.site_id = '1';

@shawno:你说得对,with 子句有缺陷,因为我误读了你的初始查询。以上为修正版。因为max 值特定于每一行,所以您已经用来获取这些值的方法可能是最有效的。优化这一点的最佳选择似乎只是将子查询从 select 子句移动到 from 子句。

另外,这是一个未经测试的解决方案,因为我既没有你的表结构也没有你的数据。在不做太多工作的情况下,我能做的最好的事情就是验证语法是否有效。

【讨论】:

+1 表示 SQL-99 语法。恕我直言,没有人应该再使用旧的 SQL-86 语法了。 @Oliie - 仍然有充分的理由选择旧语法而不是 ANSI 语法。例如,查看 Jonathan Lewis 的这篇详细博文:jonathanlewis.wordpress.com/2010/12/03/ansi-argh 感谢您的帮助。但是,我看不出您的解决方案是如何工作的——您不应该在 with 子句中也引用 table_4 吗?如果我不这样做,我会收到 ORA-00904 错误 - 标识符无效。此外,这似乎只会为 table_3.note_date 中的所有日期生成最大日期(即只返回一个日期)。我需要表 4 中每个 rec_id 的最大日期,它等于 table_3 中的一个 rec_id(其中有许多相同的 rec_id,但我只想匹配具有最大日期的那些)。这就是为什么存在嵌套相关子查询来查找最大日期的原因。有什么想法吗? 另外,我在 WITH 子句中看到您认为表 3 和 4 的最大日期(来自第二个嵌套的相关子查询),但是表 2 和 4(来自第一个嵌套的相关子查询?我看到您以“MD”的身份加入,但似乎我们现在正在比较错误的一组最大日期。 感谢您的尝试——您的最新版本也不起作用;因为我们在 (SELECT MAX(...) 相关子查询中引用了 TABLE_4,所以在上面的外部查询中需要对 TABLE_4 的引用。没有,所以我们再次得到 ORA-00904 无效标识符错误. :(

以上是关于在不使用相关子查询的情况下重写查询的主要内容,如果未能解决你的问题,请参考以下文章

没有子查询但加入的重组查询?

如何在不使用多个子查询的情况下使用多列选择多行

在不使用子查询的情况下使用 SELECT DISTINCT ON 计算总行数

将不相关子查询重写为相关子查询

有没有办法在不使用子查询的情况下根据不同的行计算平均值?

相关子查询的解决方法