知识卡片
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”不是一条可有可无的编码风格建议,而是让整套逻辑变换方法论
成立的必要条件。