当前位置: 首页 > 科技观察

神奇的SQL→为什么在GROUPBY后不能直接引用原表中的列?

时间:2023-03-18 12:37:43 科技观察

GROUPBY后SELECT列的限制标准SQL规定,对表进行聚合查询时,SELECT子句中只能写入以下三种内容:GROUPBY子句指定的聚合键,聚合函数(SUM、AVG等)、常量。我们来看一个例子:我们有一张学生班级表(tbl_student_class),数据如下:DROPTABLEIFEXISTStbl_student_class;CREATETABLEtbl_student_class(idint(8)unsignedNOTNULLAUTO_INCREMENTCOMMENT'自增主键',snovarchar(12)NOTNULLCOMMENT'学号',cnovarchar(5)NOTNULLCOMMENT'学号',cnamevarchar(20)NOTNULLCOMMENT'班级名',PRIMARYKEY(id))COMMENT='学生班级表';--------------------------------Recordsoftbl_student_class----------------------------INSERTINTOtbl_student_classVALUES('1','20190607001','0607','影视7班');INSERTINTOtbl_student_classVALUES('2','20190607002','0607','影视7班');INSERTINTOtbl_student_classVALUES('3','20190608003','0608','影视8班');INSERTINTOtbl_student_classVALUES('4','20190608004','0608','影视8班');INSERTINTOtbl_student_classVALUES('5','20190609005','0609','影视9班');INSERTINTOtbl_student_classVALUES('6','20190609006','0609','影视9班');我们要统计每个班级的学生人数(班级号,班级名)和最大的学生人数。我们应该怎么写这个查询SQL呢?我认为每个人都应该能够SELECTcno,cname,count(sno),MAX(sno)FROMtbl_student_classGROUPBYcno,cname;但是有些人会认为cno和cname本来就是一对一的,一旦cno确定了,cname也确定了现在,SQL可以这样写吗?SELECTcno,cname,count(sno),MAX(sno)FROMtbl_student_classGROUPBYcno;执行错误:[Err]1055-SELECT列表的表达式#2不在GROUPBY子句中并且包含非聚合列“test.tbl_student_class.cname”,它在功能上不依赖于GROUPBY子句中的列;这与sql_mode=only_full_group_by不兼容提示:SELECT列表中的第二个表达式(cname)不在GROUPBY子句中,它不是聚合函数;thisisincompatiblewiththesqlmode:ONLY_FULL_GROUP_BYisincompatible为什么在GROUPBY之后不能直接引用原表中的列(不在GROUPBY子句中)?别着急,我们慢慢往下看。SQL模式MySQL服务器可以运行在不同的SQL模式下,这些模式可以根据sql_mode系统变量的值对不同的客户端应用不同的方式。DBA可以设置全局SQL模式以匹配站点服务器操作要求,每个应用程序可以将其会话SQL模式设置为自己的要求。模式会影响MySQL支持的SQL语法及其执行的数据验证检查,这使得在不同环境中使用MySQL以及将MySQL与其他数据库服务器一起使用变得更加容易。更多详情请参考官网:ServerSQLModes。内容会根据MySQL的版本略有不同(包括默认值),检查时请与自己的MySQL版本保持一致。SQL模式主要分为两大类:语法支持和数据检查。常用的语法支持如下    ONLY_FULL_GROUP_BY对于GROUPBY聚合操作,如果SELECT、HAVING或ORDERBY子句中的列没有在GROUPBY中出现,那么这条SQL是非法的。ANSI_QUOTES启用ANSI_QUOTES后,双引号不能用于引用字符串,因为它们被解释为标识符,与`具有相同的效果。设置好后updatetsetf1=""...会报Unknowncolumn''infieldlist这样的语法错误。PIPES_AS_CONCAT处理||作为字符串连接运算符而不是OR运算符,这在Oracle数据库中是一样的,也类似于字符串连接函数CONCAT()NO_TABLE_OPTIONS在使用SHOWCREATETABLE时,不会输出MySQL特有的语法部分,如发动机。在使用mysqldump跨DB类型迁移时需要考虑这一点。NO_AUTO_CREATE_USER的字面意思是不自动创建用户。在给MySQL用户授权时,我们习惯使用GRANT...ON...TOdbuser一起创建用户。设置该选项后,与oracle操作类似。授权前必须建立用户数据检查类   NO_ZERO_DATE认为日期'0000-00-00'不合法,这关系到后面是否设置严格模式。1.如果设置了严格模式,那么NO_ZERO_DATE自然满足。但如果是INSERTIGNORE或UPDATEIGNORE,'0000-00-00'仍然允许,只显示warning;2.如果在非严格模式下设置NO_ZERO_DATE,效果同上,'0000-00-00'是允许的,但显示warning;如果没有设置NO_ZERO_DATE,没有警告被认为是一个完全合法的值;3、NO_ZERO_IN_DATE的情况和上面类似,不同的是控制date和day可以为0,即2010-01-00是否合法;NO_ENGINE_SUBSTITUTION使用ALTER为TABLE或CREATETABLE指定ENGINE时,需要的存储引擎被禁用或未编译怎么办。启用NO_ENGINE_SUBSTITUTION时,直接抛出错误;当这个值没有设置时,CREATE用默认的存储引擎替换它,ATLER不做任何改变,并抛出一个警告STRICT_TRANS_TABLES来设置它,表示启用了严格模式。请注意,STRICT_TRANS_TABLES不是几种策略的组合。指的是在INSERT和UPDATE中如何处理很少或无效的值:1.前面提到的将''传递给int在严格模式下是非法的。如果启用非严格模式,则变为0,产生警告;2、OutOfRange,成为插入的最大边界值;3、当要插入的新行不包含其定义中没有显式DEFAULT子句的非NULL列的值时,该列缺失值;默认模式当我们不修改配置文件时,MySQL有自己的默认模式;版本不同,默认模式也不同--查看MySQL版本SELECTVERSION();--查看sql_modeSELECT@@sql_mode;wecanSee,thedefaultmodeof5.7.21includes: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_SUBSTITUTIONandthefirstone:ONLY_FULL_GROUP_BYwillbeconstrainedwhenthenon-directROGcolumnisincludedinthequery:ONLY_FULL_GROUP_BYColumns在BY子句中。如果我们移除模式(从“严格模式”到“宽松模式”)会怎样?我们发现上面报错的SQL--SELECTcno,cname,count(sno),MAX(sno)FROMtbl_student_classGROUPBYcno;在松散模式下可以正常执行,但是一般不推荐这种配置,线上环境往往是“严格”模式而不是“松散模式”;虽然在case中不管是“严格模式”还是“松散模式”",结果是正确的,那是因为cno是唯一对应cname的,如果cno不是唯一对应cname的,那么在“松散模式”下,cname的值是随机的,会造成难以排查的问题。如果您有兴趣,可以尝试一下。那为什么会有ONLY_FULL_GROUP_BY模式呢?我们继续往下看。顺序是用来区分集合或谓词顺序的概念。在谓词逻辑中,谓词根据输入值的顺序进行分类。输入值为一行如=或BETWEEEN的谓词称为“一阶谓词”,而输入值为一组行的EXISTS谓词称为“二阶谓词”(输入值为HAVING也是一个集合,但它不是谓词)。以此类推,三阶谓词=输入值为“集合的集合的谓词”,四阶谓词=输入值为“集合的集合的集合”的谓词,但没有以上情况SQL中的第三个顺序,所以不要太担心。在Java知音?回复面试题,送你Java面试题集又叫面向集合的语言。只有从集合的角度思考才能理解SQL的强大功能。通过上图,相信大家都看出来了。我不会在这里深入解释。有兴趣的可以去查查相关资料。为什么聚合后不能引用原表中的列?很多人都知道聚合查询的局限性,但很少有人能正确理解为什么会有这样的约束。表tbl_student_class中的cname存放的是每个学生的班级信息。但需要注意的是,这里的cname只是每个学生的属性,不是组的属性,并且GROUPBY是聚合操作,操作的对象是多个学生组成的组。所以group的属性只能是average或者sum之类的统计属性,如下图询问每个学生的cname是可以的,但是询问多个学生组成的group的cname就没有意义了学生。对于小组,只有“有多少学生”或“最大的学生人数是多少?”之类的问题。合理。将适用于个体的属性强行套用到群体上,纯属分类错误;而GROUPBY的作用就是把每个元素分成若干个子集。使用GROUPBY聚合后,SQL的操作对象会从0序的“行”变为1序的“行集合”,此时,行的属性就不能用了。SQL的世界实际上是一个层次分明的等级社会。将低级概念的属性套用到高级概念上,会导致秩序混乱,这是不允许的。至此,相信大家明白为什么聚合后无法引用原表中的列了吧。单元素集也是集合当前的集合论认为单元素集是正规集。像空集一样,单例集的定义主要是为了理论上的完整性。所以对于基于集合论的SQL,当然要严格区分元素和单元素集合。因此,元素a和集合{a}之间存在非常显着的层次差异。a≠{a}这两个级别的区别对应SQL中WHERE子句和HAVING子句的区别。WHERE子句用于处理顺序为0的对象,例如“行”,而子句HAVING用于处理顺序为1的对象,例如“集合”。总结1.SQL严格区分层次,包括谓词逻辑中的层次(EXISTS)和集合论中的层次(GROUPBY);2.有级别区分,适用于个体的属性不适用于群体,这就是为什么在聚合查询的SELECT子句中不能直接引用原表中的列;3.一般来说,单元素集合的属性与其唯一元素的属性相同。这种只包含一个元素的集合让人觉得没有必要刻意把它当作一个集合来对待,但是为了保持理论的完整性,我们还是要严格区分元素和单元素集合;