☰
MySQL ONLY_FULL_GROUP_BY报错详解:原因、解决方案与避坑指南
2026/10/17 7:12:25 网站建设 项目流程

1. 从一次“突然”的报错说起:这个模式到底是什么

大概每个用MySQL做过报表或统计的开发者,都经历过这么一幕:代码在本地跑得好好的,一部署到测试环境或者客户的服务器上,SQL突然就报错了,错误码是ER_WRONG_FIELD_WITH_GROUP,提示信息大致是:

Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'your_db.your_table.some_column' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

我第一次遇到这报错的时候,第一反应是“MySQL是不是抽风了”。因为同样的SQL,本机根本没有这个问题。后来一查,发现是sql_mode里有没有ONLY_FULL_GROUP_BY的区别——本机的MySQL版本比较老或者被改过配置,没有开启这个模式,而新环境用的是MySQL 5.7及以上默认配置,这个模式默认就是开着的。

简单来说,ONLY_FULL_GROUP_BY是MySQL的一种SQL模式,它的核心要求是:**当你使用了GROUP BY进行分组查询时,SELECT列表里出现的非聚合列,必须要么出现在GROUP BY子句中,要么被聚合函数包裹。**换句话说,你不能在分组查询里随心所欲地取一个“既没分组、也没聚合”的列。

我见过很多刚接触这个报错的人,第一反应是“把严格模式关掉不就完了”。这个思路不能说错,但很危险,尤其是在生产环境里,直接改全局sql_mode可能会引发一连串意想不到的问题。这篇文章我想把ONLY_FULL_GROUP_BY的来龙去脉、报错原因、解决方案以及我在实际项目里踩过的坑一次性讲清楚,希望能帮你少走弯路。

这篇文章适合谁?我觉得只要你的工作里需要写SQL查询、做数据统计、维护MySQL数据库,都应该看看。不管你是刚入门的新手,还是已经写过几年SQL的老手,这个模式的坑几乎每个人都会遇到。区别只在于,有的人知道怎么系统性地解决,有的人只能靠“Google一个改一个”。

2. 为什么MySQL要搞出这么个“严格”模式

2.1 先聊聊SQL标准和其他数据库的行为

在SQL标准里,GROUP BY的语义非常明确:分组之后,每一组返回一行结果,这一行里的列,要么是分组的依据(GROUP BY里的列),要么是对组内所有行进行聚合计算的结果(如COUNT()、SUM()、AVG()、MAX()、MIN()等)。

这个逻辑其实很好理解:分组之后,组内可能有多行数据,如果你直接取一个不在GROUP BY里的普通列,数据库怎么知道该取哪一行的值?比如你按user_id分组,但user_name这个字段在不同记录里可能不一样,那返回的user_name到底该是谁的?

PostgreSQL、SQL Server、Oracle这些数据库,统统严格遵循这个标准,分组查询里你敢select一个没被聚合的非分组列,直接报错,没有商量余地。

但MySQL早期(5.6及更早版本)比较“野”,它允许这种写法,并且行为是:从组内随机取一条记录的值。这在数据恰好一致时看不出问题,一旦数据不一致,结果就可能和预期完全不符,而且这个“随机”还很难复现,排错非常痛苦。

MySQL 5.7开始,官方把ONLY_FULL_GROUP_BY默认开启,就是为了向SQL标准靠拢,改掉这种可能导致语义混乱的宽松行为。

2.2 报错背后的“功能依赖”概念

你可能注意到了,报错信息里有一句:

which is not functionally dependent on columns in GROUP BY clause

这句话的意思是:SELECT里出现的某个非聚合列,与GROUP BY中的列不存在功能依赖关系。

什么叫功能依赖?简单地说,如果A的值确定了,B的值也就能唯一确定,那么B功能依赖于A。比如你按主键id分组,那这个表中的其他列都功能依赖于id,因为主键能唯一确定一行,一行里的其他列自然也是唯一的。

所以,如果你GROUP BY的是主键,那么SELECT其他非聚合列其实不会报错,因为MySQL能确定这些列的值不会因为分组而产生二义性。

这个设计其实很聪明——它不是为了卡死你,而是为了杜绝“取到不确定值”的情况。MySQL允许你通过功能依赖关系合法地选择非聚合列,而不是像某些数据库那样一刀切。

2.3 三种常见报错场景

在实际开发中,我总结下来最常触发这个报错的场景大致有三类:

场景一:分组后想取组内某个无关字段

比如你想查每个用户最近的订单金额,脑子里第一反应可能是:

SELECT user_id, order_amount, MAX(create_time) FROM orders GROUP BY user_id;

这条SQL的本意可能是“查每个用户最近一笔订单的金额”,但order_amount既不在GROUP BY里,也不是聚合过的,MySQL无法确定它对应的是哪条记录,自然报错。

场景二:ORDER BY里用了非分组列

GROUP BY子句里没包含的列,也不允许出现在ORDER BY后面,除非是聚合函数。所以下面这种也会报错:

SELECT user_id, COUNT(*) FROM orders GROUP BY user_id ORDER BY order_amount;

场景三:HAVING或SELECT列表里混了没分组的表达式

比如SELECT user_id, order_amount + 100 FROM orders GROUP BY user_id,这里order_amount + 100也不是分组的或聚合的,同样报错。

理解这些场景后,解决问题就不是难事。下面我详细拆解几种解决方案,并且对比它们的优缺点。

3. 五套解决方案深度对比,别一上来就关模式

3.1 方案一:用ANY_VALUE()临时“蒙混过关”

ANY_VALUE()函数是MySQL 5.7引入的,它的作用很直白:在一个分组里随意挑一条记录的值返回。这个函数就是专门用来绕过ONLY_FULL_GROUP_BY限制的。

SELECT user_id, ANY_VALUE(order_amount), MAX(create_time) FROM orders GROUP BY user_id;

适用场景:你知道组内该字段的值都一样,或者无所谓取哪条的值,只是想绕开报错。比如按订单号分组,但订单号的上下游关联字段在组内必然一致,那用ANY_VALUE()很合适。

需要警惕的地方:ANY_VALUE()这个名字起得很诚实——它真的就是“随便取值”。如果组内该列的值不一致,你得到的结果就是不确定的。有时候你跑十次,九次结果一样,一次结果不同,排查起来极其痛苦。

我在实际项目中看到过有人用ANY_VALUE()取create_time,本意是想算时间范围,结果拿到的永远是分组内某一条随机记录的时间,统计口径直接错掉。这种坑一旦发生,数据对不上账,追责起来非常麻烦。

实操建议:ANY_VALUE()适合“我知道没问题”的场景,不适合“我猜应该没问题”的场景。如果字段值在组内确实一致,先在注释里写清楚依据,避免后人误解。

3.2 方案二:用聚合函数包裹列

这也是很常见的做法,比如把非分组列改成MAX(column)、MIN(column)、AVG(column)等形式。

SELECT user_id, MAX(order_amount), MAX(create_time) FROM orders GROUP BY user_id;

优点:绝对合规,不需要修改任何配置,语义清晰——查的就是每个用户最大的订单金额和最近的下单时间。

缺点:不是所有查询都能用聚合函数表达式。比如你想查询“每个用户最后一次下单时的订单备注内容”,用MAX(remark)虽然能取到一个值,但未必是“最后一次下单”的备注。这种“取组内某条特定记录的其他字段”的需求,简单地用聚合函数是搞不定的,需要换一种思路(后面我会讲窗口函数方案)。

3.3 方案三:重写SQL,把所有非聚合列都塞进GROUP BY

这个方案看起来最“正统”,就是把SELECT里所有非聚合列都加到GROUP BY后面。

SELECT user_id, order_amount, MAX(create_time) FROM orders GROUP BY user_id, order_amount;

这种写法在语义上完全不一样:它不再代表“按用户分组”,而是“按用户和订单金额的组合分组”。查询结果会多出很多行,每个用户的不同金额都会单独成行。

适用场景:当你确实需要按多个维度分组时,这个写法天然正确。

致命陷阱:如果你本意是按用户维度去重,只是额外带出一个无关列,那加GROUP BY列其实是“为了不报错而改变查询语义”,结果就是数据量莫名其妙变大,统计口径完全跑偏。我见过有人在运营看板里犯了这种错,原本预期每个用户一行,结果一个用户几十行,数字对不上,查了好久才发现是这个原因。

3.4 方案四:修改sql_mode,关闭ONLY_FULL_GROUP_BY

这也是网上流传最广的方案。做法嘛,就是修改配置文件或在会话里执行SET命令:

-- 仅当前会话生效(重启后失效) SET sql_mode = (SELECT REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY', '')); -- 全局生效(但重启MySQL后恢复默认) SET GLOBAL sql_mode = (SELECT REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY', '')); -- 永久生效:修改my.cnf/my.ini,在[mysqld]段下设置 -- sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'

实际风险:

  • MySQL 5.7及以上版本重启后,默认sql_mode会重新变回含ONLY_FULL_GROUP_BY的状态,除非你在配置文件里写死。很多人以为改了全局就一劳永逸,结果某次重启后问题复现,相当尴尬。
  • 更麻烦的是,sql_mode里不止ONLY_FULL_GROUP_BY一个选项。用REPLACE去掉它之后,其他选项还在,如果你不知道这些选项的含义,后续可能还会遇到别的SQL兼容性报错。

**我的明确态度:生产环境尽量不要全局关闭这个模式。**如果你确实想关,至少要组织团队做一次Review,确认现有所有SQL都不依赖这个模式的保护。否则,某个写得不严谨的SQL可能会在无声无息中返回错误数据。大家想想,报错和错误数据,哪个更可怕?报错至少能被发现,错误数据往往要过很久、甚至永远发现不了。

3.5 方案五:窗口函数,解决“取组内特定记录”的高级手段

MySQL 8.0开始支持窗口函数,这是解决ONLY_FULL_GROUP_BY相关复杂需求的一把利器。比如“查每个用户最近一笔订单的完整信息”,用窗口函数可以这么写:

SELECT user_id, order_amount, create_time FROM ( SELECT user_id, order_amount, create_time, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time DESC) AS rn FROM orders ) t WHERE rn = 1;

思路很清晰:先用窗口函数ROW_NUMBER()给每个用户的订单按时间倒序编号,然后只取编号为1的记录。这样得到的每一行都是真实存在的完整记录,不会像GROUP BY那样丢失其他字段的信息。

如果你还在用MySQL 5.7,可以借助“相关子查询”或者“自连接”实现同样效果,只是写法稍微复杂一些。比如:

SELECT o1.user_id, o1.order_amount, o1.create_time FROM orders o1 WHERE o1.create_time = ( SELECT MAX(create_time) FROM orders o2 WHERE o2.user_id = o1.user_id );

要注意的是,如果同一个用户存在两笔时间完全相同的订单,这种写法会返回多行。实务中建议再加上唯一ID的判断,比如AND o1.id = (SELECT ... ORDER BY create_time DESC, id DESC LIMIT 1),确保每次结果稳定。

3.6 各方案速览对比表

方案适用场景风险点推荐指数
ANY_VALUE()组内字段值必然相同,或无所谓取哪条值不一致时结果不确定3星
聚合函数包裹单纯的“组内最大值/最小值/求和”需求无法取到具体某条记录的其他字段4星
改写GROUP BY确实需要多维度分组统计本意是去重时会改变语义2星
关闭ONLY_FULL_GROUP_BY团队SQL已经全面Review,确定无风险错误数据风险、重启失效、配置管理混乱1星
窗口函数/子查询需要取出组内某条完整记录写法相对复杂,需要理解执行顺序5星

4. 聊聊ONLY_FULL_GROUP_BY对索引和性能的潜在影响

很多人以为ONLY_FULL_GROUP_BY只是一个“报错开关”,不报错就万事大吉。其实,这个模式对索引的利用效率也有影响,只不过被大多数人忽视了。

先说背景:MySQL的GROUP BY有两种执行方式。

第一种是松散索引扫描(Loose Index Scan),MySQL可以直接利用索引的有序性,跳过某些索引列,做到类似“跳跃扫描”的效果,不需要把分组涉及的所有数据全部加载到临时表里排序。这种方式的性能通常非常好。

第二种是紧凑索引扫描(Tight Index Scan),MySQL扫描所有满足条件的索引键,再把它们塞进临时表,通过文件排序(filesort)等方式分组。这种方式会在临时表里存放大量数据,开销要大不少。

当ONLY_FULL_GROUP_BY开启且索引设计合理时,MySQL更容易走“松散索引扫描”的优化路径,因为查询需要的所有列都在分组键或索引上有明确的逻辑依托。举个例子,如果有一个复合索引(user_id, order_date):

SELECT user_id, MAX(order_date) FROM orders GROUP BY user_id;

这个查询可以直接基于索引进行分组扫描,按user_id分组后,每个分组内最大的order_date就是索引路径上的最后一个值,无需访问表数据。

但如果查询改成:

SELECT user_id, order_amount, MAX(order_date) FROM orders GROUP BY user_id, order_amount;

显然,由于GROUP BY多了一个order_amount,原有的复合索引不一定能覆盖新的分组需求,可能就要回表读取数据并做临时表排序,性能自然下降。

还有一点需要留意,如果你为了绕过报错,在SELECT里大量使用ANY_VALUE(),优化器可能因为列来源不明确而放弃某些索引优化。虽然这不是绝对的,但在我测试过的场景里,开启严格模式并配合合理索引的SQL,执行计划往往比关闭严格模式时更清晰可控。

实操心得:不要以为“关掉严格模式就能跑得快”。恰恰相反,严格模式会逼你写出更符合语义、更容易被优化的SQL。如果你发现某个分组查询在开启ONLY_FULL_GROUP_BY之后执行计划变差了,先别急着关模式,看看能不能调整索引或改写SQL,让查询更合理地适配索引。

5. 实战案例:分组取每组最新一条记录的完整解法

这里我分享一个近期在一个实际项目里解决的问题。场景是这样的:有一个订单历史表order_history,包含user_id、order_status、operator、update_time等字段。业务上需要拉出“每个订单当前最新状态的处理人”。原始SQL是别人留下的:

SELECT order_id, order_status, operator, update_time FROM order_history GROUP BY order_id;

意图很直白,按order_id分组,取每个订单最新的一条。但这在ONLY_FULL_GROUP_BY模式下100%报错。我当时的处理步骤是:

第一步,先和业务方确认统计口径。他们说“要的是每个订单状态变更历史上最后一次记录的处理人和状态”。

第二步,确认数据特征。每个order_id对应多条历史记录,每条记录都有唯一的自增主键id,以及变更时间update_time。由于同一条记录里order_status和operator都是跟随“动作”一起变更的,所以只要定位到“每组最新的那条记录”,其他字段也就定了。

第三步,选择解法。当时数据库是MySQL 5.7,没有窗口函数可用,就用相关子查询来定位id最大的那条记录:

SELECT o1.order_id, o1.order_status, o1.operator, o1.update_time FROM order_history o1 WHERE o1.id = ( SELECT MAX(o2.id) FROM order_history o2 WHERE o2.order_id = o1.order_id );

核心逻辑:子查询在同一个order_id分组内找最大的id,主查询只保留id等于这个最大值的记录。因为id是自增主键,id最大就代表最后插入,也就是最新状态。

执行前我习惯性地用EXPLAIN看了一眼执行计划,果然对order_history的全表扫描避免不了(因为外层表的每一行都要执行一次相关子查询)。数据量大概在十万级别,性能还算能接受。如果数据量再大一个数量级,就要考虑用临时表先取order_id + MAX(id),再自连接回原表,减少重复计算:

SELECT t1.order_id, t1.order_status, t1.operator, t1.update_time FROM order_history t1 INNER JOIN ( SELECT order_id, MAX(id) AS max_id FROM order_history GROUP BY order_id ) t2 ON t1.id = t2.max_id;

这个写法先把每组最大的id在子查询中算好,再统一关联回原表,执行计划通常比相关子查询更友好,尤其适合大表。

等到将来升级到MySQL 8.0,直接换成窗口函数版即可,逻辑更清晰,性能也有保障。迁移时要注意版本间的函数兼容性,防止上线时报语法错误。

6. 关于sql_mode的深层认知与安全考量

6.1 sql_mode到底是什么

简单说,sql_mode是一组MySQL运行时的“行为开关”,它控制MySQL在SQL语法检查、数据校验、默认值处理等方面怎么表现。ONLY_FULL_GROUP_BY只是其中一员。

我见过不少开发者,遇到跟SQL模式相关的报错,第一反应就是统统干掉所有模式,让MySQL变成“老好人”。这个思路其实掩盖了问题的本质,还会连带忽略其他模式起到的保护作用。

比如STRICT_TRANS_TABLES(严格模式),它会阻止插入超长字符串或非法日期时被“静默截断/转零”,而是直接报错。如果你为了图省事把所有模式全部关掉,那数据质量的安全网就没了,坏数据会神不知鬼不觉地写进表里——这种问题比SQL报错要可怕得多,因为报错至少能让人知道出了问题,而坏数据往往要等项目上线跑数之后,才对不上账。

6.2 修改sql_mode的正确姿势

如果你确实需要修改,我建议按这几种节奏来:

  • 会话级修改,只对当前连接生效。适合排查问题、临时验证。改完之后,新开一个连接就会恢复原样,不用担心影响其他程序。
  • 全局级修改,对整个MySQL实例生效。通常用来做上线前的统一调整,但要注意:修改后如果数据库重启,可能会恢复配置文件里的默认值。你需要修改配置文件才能真正持久化。
  • 配置文件修改,永久生效。这是唯一推荐的正式做法。改了之后要重启MySQL服务。

修改之前,先看当前sql_mode长什么样:

SELECT @@sql_mode;

你可以把输出保存下来,作为版本备份,以后误改还能恢复回去。

安全提醒:任何生产环境的sql_mode变更都要走审批流程,并且在低峰期执行。改完立即用典型SQL做回归测试,确认没有副作用,再继续下一步。

如果只是想临时验证某个SQL在“开启/关闭ONLY_FULL_GROUP_BY”两种模式下的表现,可以用下面这条SQL在会话层快速切换:

-- 去掉 ONLY_FULL_GROUP_BY SET SESSION sql_mode = sys.list_drop(@@session.sql_mode, 'ONLY_FULL_GROUP_BY'); -- 加回 ONLY_FULL_GROUP_BY SET SESSION sql_mode = sys.list_add(@@session.sql_mode, 'ONLY_FULL_GROUP_BY');

MySQL的sys库提供了list_drop和list_add这两个函数,专门用来操作逗号分隔的模式列表,比用REPLACE更安全,不会误伤含有相似字符的其他模式名。

6.3 关闭严格模式后可能引发的数据审计问题

这可能是很多人没意识到的点:假如你把ONLY_FULL_GROUP_BY关掉了,某个查询在开发库里返回的结果是确定的(因为数据碰巧一致),但在生产库里数据量大了之后,结果却是不确定的。如果这个查询背后是财务对账、经营看板、库存统计,某些数字越看越怪,又查不到原因,最后才怀疑到sql_mode上——这种“隐性错误”比直接报错难查得多,甚至可能导致错误数据已对外推送,造成更大的业务影响。

我在处理一个对账项目时,就发生过类似的事。当时的系统按天统计每个渠道的支付金额,由于同一渠道可能对应多笔不同状态的单子,老开发图省事,直接在GROUP BY后取了一个非分组列。本地数据量小,结果没问题;生产数据量一大,列值不同的情况暴露出来,统计数字开始随机抖动。最终定位到这个问题的过程非常痛苦,花了整整一周。从那以后,我在任何环境都坚持开启ONLY_FULL_GROUP_BY,宁可在开发阶段暴露语法错误,也不愿意上线后跟错误数据缠斗。

7. 常见问题速查与实战避坑清单

7.1 MySQL版本升级后的“突然报错”该怎么处理

如果你刚刚从MySQL 5.6升级到5.7或更高版本,代码开始报ONLY_FULL_GROUP_BY相关的错,这完全正常。升级前先全面梳理所有业务SQL,用测试库验证,把不规范的SQL改写掉。个人经验是按这个顺序处理:

  1. 先跑一遍全量SQL日志分析,找出所有GROUP BY查询。
  2. 逐一确认是否有非聚合列、非分组列。
  3. 优先改写SQL,让语义正确。
  4. 只有极少数情况(比如第三方系统的SQL改不了),才考虑修改sql_mode。

我最近在一家公司的数据库升级项目里,就是这么干的。大概一百多条SQL,最后只有两条需要特殊处理,一条是第三方BI工具生成的,实在改不了,只能在会话级临时关闭该模式;另一条是报表查询,因为业务逻辑本身就模糊,最后和产品团队对齐需求后才改写正确。

7.2DISTINCT和ONLY_FULL_GROUP_BY有什么关系

很多新手以为DISTINCT也会受ONLY_FULL_GROUP_BY影响,其实两者没有直接关系。DISTINCT是对最终结果集去重,不涉及分组语义,不需要满足“分组列/聚合列”的约束。但是,如果你用DISTINCT加某个非分组列去重,取出来的依然是“这条记录本身”,不存在歧义,所以不会触发这个模式。

7.3 在HAVING里使用非聚合列为什么也会报错

HAVING在分组之后执行,它的条件里如果出现一个非分组列,MySQL同样无法确定是检查组内哪一条数据,所以也会受ONLY_FULL_GROUP_BY约束。正确做法是使用聚合表达式,比如HAVING COUNT(*) > 5或HAVING MAX(amount) > 100。

7.4 分组查询里能不能用别名

MySQL支持在HAVING和ORDER BY里使用SELECT别名,这是它相对宽松的地方。比如:

SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id HAVING cnt > 5;

这是合法的。但我不建议到处依赖别名,因为有些子查询或视图场景下,别名解析规则会有变化,写的SQL太“飘逸”,后续维护就费劲。

7.5 避坑清单:我在实操中总结的九条经验

  1. 别急于关模式。先定位SQL不符合规范的地方,再考虑是改SQL还是改模式。
  2. ANY_VALUE()是有副作用的,用之前必须确认组内字段值没有业务差异。
  3. 加了聚合函数不等于语义正确。比如MAX(order_amount)能取出最大金额,但如果你想取“最大金额对应的订单号”,MAX帮不了你,要靠窗口函数或子查询。
  4. GROUP BY加列等于改查询粒度。加列之前想清楚,你到底想按什么维度分组。
  5. 窗口函数是解这类问题最优雅的方案,只要数据库版本支持就优先考虑。
  6. 修改sql_mode前先备份,最好把SELECT @@sql_mode的结果存到本地,改出问题能快速还原。
  7. 生产环境改sql_mode要谨慎,尤其是多系统共用一个MySQL实例时,某个系统改了配置可能影响另一个系统的SQL行为,必须先做全量SQL回归。
  8. 在MySQL 8.0里,WITH公共表表达式可以配合窗口函数写出非常清晰的分组取最新SQL,结构上比子查询嵌套好读很多。
  9. 任何一次查询报错都值得停下来想想查询的语义,不要用“绕过报错”代替“理解需求”。

8. 写在最后:一些个人经验和体会

ONLY_FULL_GROUP_BY这个小小的模式,实际上折射出一个很根本的数据库设计哲学:数据库应当尽力保证查询结果的可预期性,而不是给开发者提供“方便”的模糊语义。很多报错并不是数据库故意刁难,而是它在提醒你:“你的查询可能不严谨,请确认后再执行。”

我在实际运维中见过太多因为关闭这个模式而埋下的隐患。有些系统跑了好几年都没问题,数据量一上来,分组字段碰巧重复,报表数字就开始跳变,然后运维和研发互相怀疑,最后一看,sql_mode早就被人改过。这种排查过程极度内耗,不如从一开始就保持严格模式,配合严谨的SQL规范。

另外再说一句,如果你负责的团队里有多个开发人员,我强烈建议把“SQL规范”纳入代码评审,重点检查GROUP BY查询是否满足ONLY_FULL_GROUP_BY要求。这一条习惯能帮你们在项目早期就规避大量和数据正确性相关的隐患,省下的排查时间非常可观。

最后分享一个小技巧:遇到ONLY_FULL_GROUP_BY报错时,先把SQL里涉及的表结构、索引和业务规则梳理一遍,搞清楚“到底要按什么分组、分组后要展示哪些列、这些列的逻辑值是什么”,然后你会发现,绝大多数问题用窗口函数或合理改写都能优雅解决,根本不需要去动sql_mode。

希望这篇文章能帮到你。如果你在实操中遇到其他GROUP BY的诡异问题,也欢迎一起交流探讨。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询