知识卡片

NULL让"逻辑等价"的SQL改写产生不同的错误答案

普通读书笔记卡

内容

查询”获得和Paris每种零件重量都不相同的零件名称”,用[[全称量词化的系统化SQL 映射步骤]]的标准流程推导出SELECT DISTINCT PX.PNAME FROM P AS PX WHERE NOT EXISTS(SELECT * FROM P AS PY WHERE PY.CITY='Paris' AND PY.WEIGHT= PX.WEIGHT)。这套推导在纯二值逻辑下完全正确,但一旦数据库里存在NULL, 结果会彻底失真:假设Paris至少有一种零件、但这些零件的WEIGHT全是NULL, 现实世界里这个查询本应”无法回答”(既不知道是否有零件和它们重量相同,也 不知道是否都不同)——但SQL会给出一个确定但错误的答案:EXISTS子查询对P里 每个PX都返回空表,NOT EXISTS恒为TRUE,于是错误地把P里所有零件名称都返回。 更糟的是,一个看起来应该逻辑等价的替代写法 SELECT DISTINCT PX.PNAME FROM P AS PX WHERE PX.WEIGHT NOT IN(SELECT PY.WEIGHT FROM P AS PY WHERE PY.CITY='Paris')在同样条件下却返回完全 不同的另一个错误结果——空结果集。这个对照直接证明了[[表达式变换法则工具 箱]]里那些逻辑法则的”逻辑等价”保证,是建立在二值逻辑([[NULL与三值逻辑 逻辑正确与现实正确的分离]])之上的,一旦NULL介入把系统实际运行在三值逻辑 下,两条本该等价的SQL表达式就可能各自给出不同的、都错误的答案,而且用户 根本无法判断哪个更接近”正确”(逻辑上唯一诚实的答案应该是”信息不足,无法 回答”,但SQL无法表达这种诚实的沉默)。这个案例把本章方法论的适用边界钉死 在一条硬性前提上:只有在数据库不含NULL的场景下,本章介绍的所有系统化变换 才能确保推导出的SQL表达式真正保留原始逻辑表述的正确性——教训直白到近乎 残酷:”避免NULL”不是一条可有可无的编码风格建议,而是让整套逻辑变换方法论 成立的必要条件。

参考来源

- 位置:《SQL与关系数据库理论——如何编写健壮的SQL代码》第11章"使用逻辑 表述SQL表达式"11.4节"例3:蕴涵和全称量词化"(源文件:OEBPS/text00122.html) - 结论依据:原文明确"EXISTS后面的子查询对于每个P中的零件型号px都会得到 一个空表。因此,NOT EXISTS对每个这样的PX都会得到TRUE,整个表达式会 错误地返回P中所有零件型号名称……下述SQL表达式……看起来好像应该和前面的 表达式逻辑等价……会返回一个空结果:一个不同的结果,尽管同样不正确…… 教训是很显然的:避免null!这样,变换都会正确地进行"。 - 原始内容:整个表达式会错误地返回P中所有零件型号名称……下述SQL表达式 ……会返回一个空结果:一个不同的结果,尽管同样不正确……教训是很显然的: 避免null!