知识卡片
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]])。