在不使用相关子查询的情况下重写查询
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 无效标识符错误. :(以上是关于在不使用相关子查询的情况下重写查询的主要内容,如果未能解决你的问题,请参考以下文章