知识卡片

相关子查询到IN子查询的变换法则及适用边界

操作参考卡

内容

用[[全称量词化的系统化SQL映射步骤]]推导出的SQL表达式常常包含相关子查询 (子查询内部WHERE条件引用外层表的列,如SPX.SNO=SX.SNO)——从性能角度看, 相关子查询原则上要对外层表的每一行都重新计算一遍,而不是像非相关子查询 那样只需整体计算一次,所以消除相关子查询往往值得研究。存在一条具体可用 的变换法则:SELECT sic FROM T1 WHERE[NOT]EXISTS(SELECT * FROM T2 WHERE T2.C=T1.C AND bx)可以改写成SELECT sic FROM T1 WHERE T1.C[NOT] IN(SELECT T2.C FROM T2 WHERE bx)——后者的子查询不再引用外层表T1,是一个 真正独立、可以单独求值一次的子查询。书中提醒这条法则值得在能用的地方尽量 应用(理想情况下优化器应该能自动完成这一步,但现实中不能总指望优化器做到 最优),但它并非总能生效:一是[[NULL让逻辑等价的SQL改写产生不同的错误 答案]]里已证明的NULL陷阱(NOT IN在含NULL的候选集合上会产生和NOT EXISTS 不同的结果);二是即使完全避免了NULL,仍然存在这条变换根本无法应用的 情况——判断某个具体的相关子查询是否落在这条法则的适用范围内,需要针对 每个具体查询单独核实,不能把这条变换当成万能的机械替换规则来无脑套用。

参考来源

- 位置:《SQL与关系数据库理论——如何编写健壮的SQL代码》第11章"使用逻辑 表述SQL表达式"11.5节"例4:相关子查询"(源文件:OEBPS/text00123.html) - 结论依据:原文明确"相关子查询必须对外层表的每一行进行重复计算,而不是 对于所有的行仅计算一遍。所以,消除相关子查询的可能性似乎是值得研究的 ……SELECT sic FROM T1 WHERE[NOT]EXISTS(SELECT*FROM T2 WHERE T2.C= T1.C AND bx)可以变换为SELECT sic FROM T1 WHERE T1.C[NOT]IN(SELECT T2.C FROM T2 WHERE bx)……此变换也有很多无法应用的场合……null就可以是 原因之一……即使是避免了null,还是有不能应用此变换的情况"。 - 原始内容:相关子查询必须对外层表的每一行进行重复计算……可以变换为 SELECT sic FROM T1 WHERE T1.C[NOT]IN(SELECT T2.C FROM T2 WHERE bx) ……此变换也有很多无法应用的场合……即使是避免了null,还是有不能应用此 变换的情况。