知识卡片

ALL或ANY比较的语义陷阱与建议

操作参考卡

内容

SQL的ALL/ANY比较(rx θ tsq,rx行表达式、tsq表子查询、θ为常用标量比较符) 语义定义为:ALL比较在tsq代表的表所有行都满足对应比较时为TRUE,空表时也 返回TRUE;ANY(或SOME)比较在tsq代表的表至少一行满足对应比较时为TRUE, 空表时返回FALSE。这里有个耐人寻味的不一致:SQL的EVERY”集合函数”(呼应 [[SQL聚集运算符的三重缺陷]])在参数为空时错误返回null,但ALL比较在参数 表为空时却正确返回TRUE——这种不一致的根源是ALL/ANY比较的语义早在NULL被 引入SQL之前就已经定义好了。书中的核心建议是不要使用ALL或ANY比较,理由 有二:一是自然语言里”每一个(every)”和”任意一个(any)”经常可以互换, 极易导致该用ALL的地方误用ANY(比如”比每个蓝色零件都重”应该用>ALL,但 自然表述里”任意一个蓝色零件”很容易被误读成>ANY);二是ALL/ANY比较总能 被改写成语义更直白的EXISTS表达式(比如CITY<>ANY(SELECT CITY FROM P) 表面看像”和任何零件所在城市都不同”,实际逻辑等价于”存在至少一个零件在 不同城市”,直接用EXISTS(...WHERE P.CITY<>S.CITY)表达更不容易读错)。 两个重要例外是=ANY等价于IN、<>ALL等价于NOT IN,这两种情况用IN/NOT IN 改写通常更清晰。ALL/ANY比较也常能改写成含MAX/MIN的表达式(”比集合里所有 值都大”等价于”比集合最大值还大”),但这条改写路径藏着一个致命陷阱:SQL 把空集合的MAX/MIN定义为null(不是恒等值),所以当子查询结果为空时, WEIGHT>ALL(...)会正确返回TRUE,但等价改写后的WEIGHT>(SELECT MAX(...)) 却会因为MAX返回null而让整个比较变成UNKNOWN,进而在WHERE子句里被当作 FALSE处理,两者在空集合场景下给出完全相反的结果——要保证这条改写始终 成立,必须给MAX/MIN套上COALESCE兜底(呼应[[避免null的实践规则NOT_NULL 约束与COALESCE]])。

参考来源

- 位置:《SQL与关系数据库理论——如何编写健壮的SQL代码》第11章"使用逻辑 表述SQL表达式"11.12节"例11:ALL或ANY比较"(源文件:OEBPS/text00130.html) - 结论依据:原文明确"如果表为空表,则ALL比较返回TRUE……如果表为空表, 则ALL比较返回FALSE(此处指ANY)……不要使用ALL或ANY比较——它们易于出错, 而且总是可以用别的方法来达到它们的效果……包含MAX和MIN的变换在MAX或MIN 参数为空集合的情况下不保证会正常进行。原因在于,SQL定义空集合的MAX和 MIN为null"。 - 原始内容:不要使用ALL或ANY比较——它们易于出错,而且总是可以用别的方法 来达到它们的效果……包含MAX和MIN的变换在MAX或MIN参数为空集合的情况下 不保证会正常进行。原因在于,SQL定义空集合的MAX和MIN为null。