知识卡片

子查询三分类及空表型转规则

普通读书笔记卡

内容

SQL子查询是括号封闭的表表达式(但不能是显式JOIN表达式本身——(A NATURAL JOIN B)不合法,必须写成(SELECT * FROM A NATURAL JOIN B)),按出现的 语法位置分三类,语法完全相同但型转规则不同:表子查询(不需要型转,直接 代表一张表,如WHERE CITY IN(SELECT CITY FROM P WHERE COLOR='Red')); 行子查询(出现在本应是行表达式的位置,要求代表恰好一行的表,这一行会型转 为对应的行——若结果实际是多行则触发错误,若结果是空表则被当作”仅一行、 每列都是NULL”处理);标量子查询(出现在本应是标量表达式的位置,要求代表 恰好一行一列的表,型转两次:先型转为那一行,再型转为那一列的值——若结果 有多列在编译期报错,多行在运行期报错,若结果是空表则被当作”一行、唯一 值为NULL”处理)。这套”空结果自动型转成NULL”的规则值得高度警惕,是 [[避免null的实践规则NOT_NULL约束与COALESCE]]里提到的NULL几大来源之一, 也是很多看似合理的查询在边界情况下悄悄产生错误结果的根源(呼应[[NULL让 逻辑等价的SQL改写产生不同的错误答案]]里已经证明过的具体案例)。相关子查询 是这三类子查询里包含”外层表引用”的特殊情形(比如WHERE 'P1' IN(SELECT PNO FROM SP WHERE SP.SNO=S.SNO)里对外层S的引用)——[[相关子查询到IN子 查询的变换法则及适用边界]]中已说明它在性能上通常应该被尽量避免,因为 概念上它要对外层表每一行都重新求值一次,而不是整体求值一次。

参考来源

- 位置:《SQL与关系数据库理论——如何编写健壮的SQL代码》第12章"关于SQL的 其他主题"12.5节"子查询"(源文件:OEBPS/text00138.html) - 结论依据:原文明确"表达式tx不能是一个显式JOIN表达式……行子查询……如果 rsq不代表仅有一行的表,那么……在它代表根本没有行的表时,会认为对应的表 仅包含一行,而该行在每个列的位置包含null……标量子查询……在它代表一列 但没有任何行时,认为对应的表仅包含一行,且行中包含唯一一个null""相关 子查询……从性能角度出发经常禁用相关子查询,因为它们对于外层表的每一行 都要进行一次求值而不是对整体仅进行一次求值"。 - 原始内容:在它代表根本没有行的表时,会认为对应的表仅包含一行,而该行在 每个列的位置包含null……从性能角度出发经常禁用相关子查询,因为它们对于 外层表的每一行都要进行一次求值而不是对整体仅进行一次求值。