MySQL Workbench中正常运行的SQL在PHP执行时部分列返回NULL
问题根源
出现多列返回NULL的核心原因是MySQL 用户自定义变量(@total这类)的执行顺序没有官方明确保障,不同数据库连接场景下的赋值时机存在明显差异。
- 你在MySQL Workbench中能跑出正确结果,是因为Workbench的连接会话对变量赋值逻辑做了兼容处理,会按你写的SQL顺序先完成
@total := SUM(ade.total_co2_kg)的赋值,后续引用@total做除法的计算列自然能拿到正确的总数值。 - 但PHP侧的数据库连接(你代码里用的是GLPI框架封装的
$DB类,底层是标准PDO/mysqli驱动),完全遵循MySQL官方的规则:SELECT列表中变量的赋值时机、和其他列计算的先后顺序是不固定的。再加上PHP每次发起请求都会新建独立的数据库会话,@total变量在刚建连的时候默认值是NULL,非常容易出现“后面引用@total的计算列,在@total完成赋值之前就执行了除法计算”的情况——用NULL做除数算出来的结果自然就是NULL。 - EverSQL找不到具体问题的原因也很简单:你的SQL本身是符合语法规范的,只是存在未定义的逻辑行为,这类跨列引用变量的顺序问题,静态语法校验工具本来就检测不出来。
解决方法
直接废弃不必要的用户自定义变量写法就行,完全不需要靠@total做中间值传参,把聚合计算的表达式直接复用到每个计算列中,就能彻底规避顺序带来的问题:
- 修改后可跨环境稳定运行的SQL参考:
SELECT CONCAT(ade.`type`, ' ', ade.subtype) AS `type`, COUNT(*) AS count, SUM(ade.total_co2_kg) AS total, ROUND(SUM(ade.raw_materials) / SUM(ade.total_co2_kg) * 100, 2) AS raw_materials, ROUND(SUM(ade.supplying) / SUM(ade.total_co2_kg) * 100, 2) AS supplying, ROUND(SUM(ade.making) / SUM(ade.total_co2_kg) * 100, 2) AS making, ROUND(SUM(ade.assembly) / SUM(ade.total_co2_kg) * 100, 2) AS assembly, ROUND(SUM(ade.distribution) / SUM(ade.total_co2_kg) * 100, 2) AS distribution FROM glpi_plugin_gtm_computermodels_association AS ass_mod JOIN glpi_plugin_gtm_ademe_co2_emissions_for_manufacture AS ade ON ass_mod.gtm_ademe_id = ade.id GROUP BY ade.`type`, ade.subtype ORDER BY total DESC
- 这种写法能解决问题的逻辑很简单:所有计算列的分母都是独立计算的聚合值,和SQL解析顺序、变量赋值时机完全脱钩,不管在Workbench、PHP网页还是其他数据库客户端跑,结果都能保持一致。
- 完全不推荐的替代思路:如果非要用用户变量,就得在查询前先执行
SET @total = 0;完成变量初始化,还得让数据库连接支持多语句一次性执行。这种写法不仅容易触发数据库的安全配置限制,也没法从根源上解决变量执行顺序未定义的问题,没有实际性价比。
补充:很多人写SQL时默认SELECT子句是从左到右逐行执行的,但MySQL官方明确说明,只要查询中包含用户变量赋值,整体执行顺序就是未定义的,生产环境绝对不能依赖这类逻辑写业务SQL。
内容的提问来源于stack exchange,提问作者Rémi
相关产品推荐
相关产品推荐

