神奇的 SQL 之层级 → 为什么 GROUP BY 之后不能直接引用原表中的列
前言
开心一刻
感觉不妙呀,弟弟舔它! 不该舔的,舔到怀疑人生了......
GROUP BY 后 SELECT 列的限制
标准 SQL 规定,在对表进行聚合查询的时候,只能在 SELECT 子句中写下面 3 种内容:通过 GROUP BY 子句指定的聚合键、聚合函数(SUM 、AVG 等)、常量。我们来看个例子
我们有 学生班级表(tbl_student_class) 以及 数据如下 :
DROP TABLE IF EXISTS tbl_student_class;
CREATE TABLE tbl_student_class (
id int(8) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
sno varchar(12) NOT NULL COMMENT '学号',
cno varchar(5) NOT NULL COMMENT '班级号',
cname varchar(20) NOT NULL COMMENT '班级名',
PRIMARY KEY (id)
) COMMENT='学生班级表'; -- ----------------------------
-- Records of tbl_student_class
-- ----------------------------
INSERT INTO tbl_student_class VALUES ('', '', '', '影视7班');
INSERT INTO tbl_student_class VALUES ('', '', '', '影视7班');
INSERT INTO tbl_student_class VALUES ('', '', '', '影视8班');
INSERT INTO tbl_student_class VALUES ('', '', '', '影视8班');
INSERT INTO tbl_student_class VALUES ('', '', '', '影视9班');
INSERT INTO tbl_student_class VALUES ('', '', '', '影视9班');
我们想统计各个班(班级号、班级名)一个有多少人、以及最大的学号,我们该怎么写这个查询 SQL ? 我想大家应该都会
SELECT cno,cname,count(sno),MAX(sno)
FROM tbl_student_class
GROUP BY cno,cname;
可是有人会想了,cno 和 cname 本来就是一对一,cno 一旦确定,cname 也就确定了,那 SQL 是不是可以这么写 ?
SELECT cno,cname,count(sno),MAX(sno)
FROM tbl_student_class
GROUP BY cno;
执行报错了:
[Err] 1055 - Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'test.tbl_student_class.cname' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
提示信息:SELECT 列表中的第二个表达式(cname)不在 GROUP BY 的子句中,同时它也不是聚合函数;这与 sql 模式:ONLY_FULL_GROUP_BY 不相容。
为什么 GROUP BY 之后不能直接引用原表(不在 GROUP BY 子句)中的列 ? 莫急,我们慢慢往下看。
SQL 模式
MySQL 服务器可以在不同的 SQL 模式下运行,并且可以针对不同的客户端以不同的方式应用这些模式,具体取决于 sql_mode 系统变量的值。 DBA 可以设置全局SQL模式以匹配站点服务器操作要求,并且每个应用程序可以将其会话 SQL 模式设置为其自己的要求。模式会影响 MySQL 支持的 SQL 语法以及它执行的 数据验证检查,这使得在不同环境中使用MySQL以及将MySQL与其他数据库服务器一起使用变得更加容易。更多详情请查阅官网:Server SQL Modes。MySQL 版本不同,内容会略有不同(包括默认值),查阅的时候注意与自身的 MySQL 版本保持一致。
SQL 模式主要分两类:语法支持类和数据检查类,常用的如下
语法支持类
ONLY_FULL_GROUP_BY
对于 GROUP BY 聚合操作,如果在 SELECT 中的列、HAVING 或者 ORDER BY 子句的列,没有在GROUP BY中出现,那么这个SQL是不合法的
ANSI_QUOTES
启用 ANSI_QUOTES 后,不能用双引号来引用字符串,因为它被解释为识别符,作用与 ` 一样。设置它以后,update t set f1="" ...,会报 Unknown column ‘’ in field list 这样的语法错误
PIPES_AS_CONCAT
将 || 视为字符串的连接操作符而非 或 运算符,这和Oracle数据库是一样的,也和字符串的拼接函数 CONCAT() 相类似
NO_TABLE_OPTIONS
使用 SHOW CREATE TABLE 时不会输出MySQL特有的语法部分,如 ENGINE ,这个在使用 mysqldump 跨DB种类迁移的时候需要考虑
NO_AUTO_CREATE_USER
字面意思不自动创建用户。在给MySQL用户授权时,我们习惯使用 GRANT ... ON ... TO dbuser 顺道一起创建用户。设置该选项后就与oracle操作类似,授权之前必须先建立用户
数据检查类
NO_ZERO_DATE
认为日期 ‘0000-00-00’ 非法,与是否设置后面的严格模式有关
1、如果设置了严格模式,则 NO_ZERO_DATE 自然满足。但如果是 INSERT IGNORE 或 UPDATE IGNORE,’0000-00-00’依然允许且只显示warning;
2、如果在非严格模式下,设置了NO_ZERO_DATE,效果与上面一样,’0000-00-00’ 允许但显示warning;如果没有设置NO_ZERO_DATE,no warning,当做完全合法的值;
3、NO_ZERO_IN_DATE情况与上面类似,不同的是控制日期和天,是否可为 0 ,即 2010-01-00 是否合法;
NO_ENGINE_SUBSTITUTION
使用 ALTER TABLE 或 CREATE TABLE 指定 ENGINE 时, 需要的存储引擎被禁用或未编译,该如何处理。启用 NO_ENGINE_SUBSTITUTION 时,那么直接抛出错误;不设置此值时,CREATE用默认的存储引擎替代,ATLER不进行更改,并抛出一个 warning
STRICT_TRANS_TABLES
设置它,表示启用严格模式。注意 STRICT_TRANS_TABLES 不是几种策略的组合,单独指 INSERT、UPDATE 出现少值或无效值该如何处理:
1、前面提到的把 ‘’ 传给int,严格模式下非法,若启用非严格模式则变成 0,产生一个warning;
2、Out Of Range,变成插入最大边界值;
3、当要插入的新行中,不包含其定义中没有显式DEFAULT子句的非NULL列的值时,该列缺少值;
默认模式
当我们没有修改配置文件的情况下,MySQL 是有自己的默认模式的;版本不同,默认模式也不同
-- 查看 MySQL 版本
SELECT VERSION(); -- 查看 sql_mode
SELECT @@sql_mode;
我们可以看到,5.7.21 的默认模式包含:
ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
而第一个:ONLY_FULL_GROUP_BY 就会约束:当我们进行聚合查询的时候,SELECT 的列不能直接包含非 GROUP BY 子句中的列。那如果我们去掉该模式(从“严格模式”到“宽松模式”)呢 ?
我们发现,上述报错的 SQL
-- 宽松模式下 可以执行
SELECT cno,cname,count(sno),MAX(sno)
FROM tbl_student_class
GROUP BY cno;
能正常执行了,但是一般情况下不推荐这样配置,线上环境往往是“严格模式”,而不是“宽松模式”;虽然案例中,无论是“严格模式”,还是“宽松模式”,结果都是对的,那是因为 cno 与 cname 唯一对应的,如果 cno 与 cname 不是唯一对应,那么在“宽松模式下” cname 的值是随机的,这就会造成难以排查的问题,有兴趣的可以去试试。那为什么会有 ONLY_FULL_GROUP_BY 模式呢 ? 我们继续往下看
阶
阶(order)是用来区分集合或谓词的阶数的概念。谓词逻辑中,根据输入值的阶数对谓词进行分类。= 或者 BETWEEEN 等输入值为一行的谓词叫作"一阶谓词",而像 EXISTS 这样输入值为行的集合的谓词叫作"二阶谓词"(HAVING 的输入值也是集合,但它不是谓词)。以此类推,三阶谓词=输入值为"集合的集合"的谓词,四阶谓词=输入值为"集合的集合的集合"的谓词,但是 SQL 里并不会出现三阶以上的情况,所以不用太在意。简单点如下图
谈到了阶,就不得不谈下集合论;集合论是 SQL 语言的根基,因为它的这个特性,SQL 也被称为面向集合语言。只有从集合的角度来思考,才能明白 SQL 的强大威力。通过上图,相信大家也都能看到,这里不做更深入的讲解了,有兴趣的可以去查相关资料。
为什么聚合后不能再引用原表中的列
很多人都知道聚合查询的限制,但是很少有人能正确地理解为什么会有这样的约束。表 tbl_student_class 中的 cname 存储的是每位学生的班级信息,但需要注意的是,这里的 cname 只是每个学生的属性,并不是小组的属性,而 GROUP BY 又是聚合操作,操作的对象就是由多个学生组成的小组,因此,小组的属性只能是平均或者总和等统计性质的属性,如下图
询问每个学生的 cname 是可以的,但是询问由多个学生组成的小组的 cname 就没有意义了。对于小组来说,只有"一共多少学生"或者"最大学号是多少?"这样的问法才是有意义的。强行将适用于个体的属性套用于团体之上,纯粹是一种分类错误;而 GROUP BY 的作用是将一个个元素划分成若干个子集,使用 GROUP BY 聚合之后,SQL 的操作对象便由 0 阶的"行"变为了 1 阶的"行的集合",此时,行的属性便不能使用了。SQL 的世界其实是层级分明的等级社会,将低阶概念的属性用在高阶概念上会导致秩序的混乱,这是不允许的。此时我相信大家都明白:为什么聚合后不能再引用原表中的列 。
单元素集合也是集合
现在的集合论认为单元素集合是一种正常的集合。单元素集合和空集一样,主要是为了保持理论的完整性而定义的。因此对于以集合论为基础的 SQL 来说,当然也需要严格地区分元素和单元素集合。因此,元素 a 和集合 {a} 之间存在着非常醒目的层级差别。
a ≠ {a}
这两个层级的区别分别对应着 SQL 中的 WHERE 子句和 HAVING 子句的区别。WHERE 子句用于处理"行"这种 0 阶的对象,而 HAVING 子句用来处理"集合"这种 1 阶的对象。
总结
1、SQL 严格区分层级,包括谓词逻辑中的层级(EXISTS),也包括集合论中的层级(GROUP BY);
2、有了层级区分,那么适用于个体上的属性就不适用于团体了,这也就是为什么聚合查询的 SELECT 子句中不能直接引用原表中的列的原因;
3、一般来说,单元素集合的属性和其唯一元素的属性是一样的。这种只包含一个元素的集合让人觉得似乎没有必要特意地当成集合来看待,但是为了保持理论的完整性,我们还是要严格区分元素和单元素集合;
参考
《SQL基础教程》
《SQL进阶教程》
神奇的 SQL 之层级 → 为什么 GROUP BY 之后不能直接引用原表中的列的更多相关文章
- [转]神奇的 SQL 之层级 → 为什么 GROUP BY 之后不能直接引用原表中的列
原文:https://www.cnblogs.com/youzhibing/p/11516154.html 这篇文章,对group by的讲解不错 -------------------------- ...
- sql得到表中的列信息
取列全部用的 sys. 中的表 CTE:WITH name AS() 用法: sql树形查询 ①主键信息 SELECT ic.column_id, ic.index_column_id, ic.o ...
- SQL笔记----在一个关系表中操作列
使用alter关键字,可以为一个表添加新的列. 比如: 给Persons的表中添加一列,名字为Birthday,类型是date. ALTER TABLE Persons ADD Birthday da ...
- mssql sqlserver 使用sql脚本检测数据表中一列数据是否连续的方法分享
原文地址:http://www.maomao365.com/?p=7335 摘要: 数据表中,有一列是自动流水号,由于各种操作异常原因(或者插入失败),此列数据会变的不连续,下文将讲述使用sql ...
- 用SQL语句将远程SQL Server数据库中表数据导入到本地数据库相应的表中
一.方法一 访问不同电脑上的数据库(远程访问,只好联好网就一样),如果经常访问或数据量较大,建议用链接服务器方法. 1.创建链接服务器 exec sp_addlinkedserver ‘srv_lnk ...
- 记一次神奇的sql查询经历,group by慢查询优化
一.问题背景 现网出现慢查询,在500万数量级的情况下,单表查询速度在30多秒,需要对sql进行优化,sql如下: 我在测试环境构造了500万条数据,模拟了这个慢查询. 简单来说,就是查询一定条件下, ...
- 为SQL Server表中的列添加/修改/删除注释属性(sp_addextendedproperty、sp_updateextendedproperty、sp_dropextendedproperty)
本篇基本完全参考:sql--sp_addextendedproperty和sp_updateextendedproperty (Transact-SQL) 三个存储过程用法一样,以sp_addexte ...
- sql insert into select语句写法-将查询结果直接插入到表中
insert into month_gpcj_info(idStr,zszrmygpsl,xyzrmygpsl,mycje,mycjl,month_date,dataCompiledDate) sel ...
- 【SQL】Update中使用表别名、如何用表中一列值替换另一列的所有值
Update中使用表别名 select中的表别名: select * from TableA as ta update中的表别名: update ta from TableA as ta 如何用表中一 ...
随机推荐
- docker-compose 综合训练
Docker-compose综合训练 一. 实验目的: 熟悉Docker-compose的基本工作原理: 熟悉Docker-compose安装 熟悉Docker compose命令基础 熟悉Docke ...
- 神盘GCCX,2019必撸大毛!
自从今年5月转型投资以来,已经很少薅羊毛了! 不是不撸,是因为一般的羊毛我真看不上! 撸羊毛能不能发财,能不能日入几百几千! 答案是,可以! 干羊毛,像趣步,云钱包,云比特,环保币,很多人都发财了!前 ...
- 性能测试学习第六天-----JMeter拓展应用
一.TCP取样器 服务器名称或IP:填写socket接口的ip 端口号:写socket接口的端口号 Re-use connection:是否重用链接,如果选择,同一个线程执行的所有请求都会使用一个tc ...
- 为什么不建议在hbase中使用过多的列簇
我们知道,hbase表可以设置一个至多个列簇(column families),但是为什么说越少的列簇越好呢? 官网原文: HBase currently does not do well with ...
- 良许 | 命令的输出不会保存?居然连 tee 命令都不会用!
很多情况下,我们需要保存程序/命令的输出到本地,常用的一种方法是重定向,这也是一种很好的方法.但有个问题,如果你想要做后续操作,比如要统计输出的行数等,重定向就有困难了. 这时候,tee 命令就派上用 ...
- python 10 动态参数
目录 1. 函数的动态参数 1.1 动态位置参数(*arges) 1.2 动态关键字参数 (**kwargs) 1.3 万能传参: 2. 函数的注释 3. 名称空间 4. 函数嵌套 5. 函数变量修改 ...
- 常用Linux备份
用于备份的Tar 备份工具Tar是以前备份文件的可靠方法,几乎可以工作于任何环境中,Linux老用户一般都信赖它. Linux中以.tar结尾的文件都是用tar创建的.它的使用超出了单纯的备份,可用来 ...
- HDU 6055
题意略. 思路:要你找出所有正多边形,其实是唬人的,整点的正多边形只有正方形,具体证明可以参考 2017国家队论文集-<正多边形>-杨景钦 详见代码: #include<bi ...
- 并发新特性—Executor框架与线程池
http://blog.csdn.net/ns_code/article/details/17465497 Executor框架简介 在Java5之后,并发编程引入了一堆新的启动.调度和管理线程的AP ...
- Delphi - Indy TIdFTPServer封装类
在Delphi 7开发下有强大的Indy控件,版本为9,要实现一个FTP服务器,参考自带的例子,发现还要写很多函数,而且不支持中文显示文件列表等等. 于是,自己改进封装了下,形成一个TFTPServe ...