知识卡片
SQL聚集运算符的三重缺陷
内容
关系模型的聚集运算符(COUNT/SUM/AVG/MAX/MIN/AND/OR/XOR)从某关系的某属性
“聚集”出一个标量结果,Tutorial D里SUM(SP,QTY)这类调用能作为独立表达式直接
出现。SQL的对应写法看似做同样的事,实则存在三重缺陷:第一,SQL其实根本不
支持真正的聚集运算符——SELECT COUNT(*) FROM S这类表达式得到的不是一个标量,
而是一个只有一行一列的表,只是这个表恰好又被隐式型转成它包含的那个标量值,
本质上SQL把聚集当成汇总(GROUP BY形式,见[[SUMMARIZE与GROUP_BY_HAVING的逻辑
冗余性及陷阱]])的特例来处理,SELECT COUNT(*) AS X FROM S实际是
...GROUP BY()(空分组列表)的缩写,这个”表→行→标量”的双重型转掩盖了SQL
的”聚集”和真正意义上聚集运算符之间的本质差异。第二,[[类型的正式定义与类型
生成器]]中已知SUM/COUNT/AVG等在Tutorial D里对空参数有干净的恒等值语义(0对
加法恒等所以SUM空集合理应为0,TRUE对AND恒等,MAX/MIN对空集无意义应报异常),
但对应的SQL集合函数在参数为空时几乎全部错误地返回null(唯独COUNT和COUNT(*)
正确返回0),这直接呼应[[避免null的实践规则NOT_NULL约束与COALESCE]]里必须
套COALESCE处理空聚集结果的实践规则,根源正是这里。第三,SQL标准对”空表GROUP
BY”存在一条特设规则打破了自身逻辑一致性:一般而言,一个空表分组后应当得到
0个分组(因此最终结果也是空),但SQL标准明确规定”没有分组列时,把整张表T
当作唯一一个分组”,导致对空表S执行GROUP BY()反而得到唯一一个分组(这个组
本身为空),COUNT(*)对这个空组求值返回0——这个特例本身”看起来更符合直觉”,
但书中引用Wittgenstein”所有逻辑区别都是大区别”,强调这种为了追求实用结果而
悄悄破坏逻辑一致性的做法,对任何严格建立在逻辑基础上的系统而言都是不可接受的
设计瑕疵。