MySQL中无GROUP BY情况下直接使用HAVING语句的问题探究
今天有同学给我反应,有一张表,id是主键,这样的写法可以返回一条记录:
“SELECT*FROMtHAVINGid=MIN(id);”
但是只是把MIN换成MAX,这样返回就是空了:
“SELECT*FROMtHAVINGid=MAX(id);”
这是为什么呢?
我们先来做个试验,验证这种情况。
这是表结构,初始化两条记录,然后试验:
root@localhost:plx10:25:10>showcreatetablet2G ***************************1.row*************************** Table:t2 CreateTable:CREATETABLE`t2`( `a`int(11)DEFAULTNULL, `id`int(10)unsignedNOTNULLAUTO_INCREMENT, PRIMARYKEY(`id`) )ENGINE=InnoDBAUTO_INCREMENT=5DEFAULTCHARSET=utf8 root@localhost:plx10:25:15>select*fromt2; +------+----+ |a|id| +------+----+ |1|1| |1|3| +------+----+ 2rowsinset(0.00sec) root@localhost:plx10:25:20>SELECT*FROMt2HAVINGid=MIN(id); +------+----+ |a|id| +------+----+ |1|1| +------+----+ 1rowinset(0.00sec) root@localhost:plx10:25:30>SELECT*FROMt2HAVINGid=MAX(id); Emptyset(0.00sec)
初看之下,好像真的是这样哎,怎么会这样呢?
我再试一下,把a字段改一个为10,然后试下a字段:
root@localhost:plx10:26:58>select*fromt2; +------+----+ |a|id| +------+----+ |10|1| |1|3| +------+----+ 2rowsinset(0.00sec) root@localhost:plx10:28:20>SELECT*FROMt2HAVINGa=MAX(a); +------+----+ |a|id| +------+----+ |10|1| +------+----+ 1rowinset(0.00sec) root@localhost:plx10:28:28>SELECT*FROMt2HAVINGa=MIN(a); Emptyset(0.00sec)
我擦,这回MAX能返回,MIN不能了,这又是为啥呢?
旁白
一般来说,HAVING子句是配合GROUPBY使用的,单独使用HAVING本身是不符合规范的,
但是MySQL会做一个重写,加上一个GROUPBYNULL,”SELECT*FROMtHAVINGid=MIN(id)”会被重写为”SELECT*FROMtGROUPBYNULLHAVINGid=MIN(id)”,这样语法就符合规范了。
继续……
但是,这个GROUPBYNULL会产生什么结果呢?经过查看代码和试验,可以证明,GROUPBYNULL等价于LIMIT1:
root@localhost:plx10:25:48>SELECT*FROMt2GROUPBYNULL; +------+----+ |a|id| +------+----+ |10|1| +------+----+ 1rowinset(0.00sec)
也就是说,GROUPBYNULL以后,只会有一个分组,里面就是第一行数据。
但是如果这样,MIN、MAX结果应该是一致的,那也不应该MAX和MIN一个有结果,一个没结果啊,这是为什么呢,再做一个测试。
修改一下数据,然后直接查看MIN/MAX的值:
root@localhost:plx10:26:58>select*fromt2; +------+----+ |a|id| +------+----+ |10|1| |1|3| +------+----+ 2rowsinset(0.00sec) root@localhost:plx10:27:04>SELECT*FROMt2GROUPBYNULL; +------+----+ |a|id| +------+----+ |10|1| +------+----+ 1rowinset(0.00sec) root@localhost:plx10:30:21>SELECTMAX(a),MIN(a),MAX(id),MIN(id)FROMt2GROUPBYNULL; +--------+--------+---------+---------+ |MAX(a)|MIN(a)|MAX(id)|MIN(id)| +--------+--------+---------+---------+ |10|1|3|1| +--------+--------+---------+---------+ 1rowinset(0.00sec)
是不是发现问题了?
MAX/MIN函数取值是全局的,而不是LIMIT1这个分组内的。
因此,当GROUPBYNULL的时候,MAX/MIN函数是取所有数据里的最大和最小值!
所以啊,”SELECT*FROMtHAVINGid=MIN(id)”本质上是”SELECT*FROMtHAVINGid=1″,就能返回一条记录,而”SELECT*FROMtHAVINGid=MAX(id)”本质上是”SELECT*FROMtHAVINGid=3″,当然没有返回记录,这就是问题的根源。
测试一下GROUPBYa,这样就对了,每个分组内只有一行,所以MAX/MIN一样大,这回是取得组内最大和最小值。
root@localhost:plx11:29:49>SELECTMAX(a),MIN(a),MAX(id),MIN(id)FROMt2GROUPBYa; +--------+--------+---------+---------+ |MAX(a)|MIN(a)|MAX(id)|MIN(id)| +--------+--------+---------+---------+ |1|1|3|3| |10|10|5|5| +--------+--------+---------+---------+ 2rowsinset(0.00sec)
GROUPBYNULL时MAX/MIN的行为,是这个问题的本质,所以啊,尽量使用标准语法,玩花样SQL之前,一定要搞清楚它的行为是否与理解的一致。