SQL - 使用MAX()实现行转列的一个坑 - 当GROUP BY分组后,相同粒度下的记录多于一条时,需要多加注意

    昨天遇到了一个问题,使用 MAX() 进行行转列时出现错误。然后第一次在 Stack Overflow 问问题,有幸得到大佬的帮助,贴上地址:MySQL-What’s the difference between these two sql?CASE WHEN in MAX(),one uses null,one uses a character

问题描述

    首先看一下源数据Courses:

namecourse
MarryUNIX
EricSQL
BobJava
CherrySQL
JohnSQL
JohnUNIX
BobSQL

    现在希望得到下面的结果:

nameSQLUNIXJAVA
Bobx
Cherryxx
Ericxx
Johnx
Marryxx

    这还不简单?直接套模板:

SELECT name,
   max(CASE WHEN course = 'SQL' THEN '○' ELSE 'x' END) AS "SQL",
   max(CASE WHEN course = 'UNIX' THEN '○' ELSE 'x' END) AS "UNIX",
   max(CASE WHEN course = 'Java' THEN '○' ELSE 'x' END) AS "Java"
FROM Courses
GROUP BY name;

    哎呦尼玛,结果怎么就错了

nameSQLUNIXJAVA
Bobxxx
Cherryxx
Ericxx
Johnxxx
Marryxx

    经大佬指点,原来 ‘○’ 不是 ‘o’,而且这个与 ‘○’ 和 ‘x’ 的大小有关。那我们看一下这两个的大小:

SELECT CASE WHEN '○' > 'x' THEN 'true' ELSE 'false' END;

运行结果:false

    所以 ‘○’ 的大小小于 ‘x’,‘○’ < ‘x’。

查找原因

    为了简化问题,我将SQL改成这样:

SELECT name,
  max(CASE WHEN course = 'SQL' THEN 1 ELSE 2 END) AS "SQL",
  max(CASE WHEN course = 'UNIX' THEN 1 ELSE 2 END) AS "UNIX",
  max(CASE WHEN course = 'Java' THEN 1 ELSE 2 END) AS "Java"
FROM Courses
GROUP BY name;

    运行结果如下(错误):

nameSQLUNIXJAVA
Bob222
Cherry122
Eric122
John222
Marry212

    我们希望的正确结果如下:

nameSQLUNIXJAVA
Bob121
Cherry122
Eric122
John112
Marry212

    那问题来了,为什么 1 会被 2 覆盖了?MAX() 里面不就两个分支吗?如果有对应的course就为1,否则为2?
    原因很简单,以Bob为例,GROUP BY name,Bob有一条记录是SQL的,那么执行一次SELECT后的语句就有 1 2 2,然后有一条记录是Java的,执行分支语句后为 2 2 1,由于这里使用了 MAX(),如果一列中有更大的,则选择更大的,所以最终结果一个为 2 2 2

				SQL UNIX JAVA
Bob SQL  ---->   1    2    2    <----之前没数据,执行了MAX()的结果
Bob JAVA ---->   2    2    1    <----只执行了MAX()里面的分支语句,还没执行MAX()
		 ---->   2    2    2    <----执行了MAX()的结果

    简单来说就是,当GROUP BY分组后,相同粒度下的记录多于一条时(比如name粒度(相当于主键,用于区分记录)下,Bob有两条记录),如果ELSE分支后面的数比前面的数大,由于MAX()会取最大值,那么之前记录中有true的记录会被覆盖。

解决方案

  1. 利用聚合函数忽略null的特点
SELECT name,
       max(CASE WHEN course = 'SQL' THEN '○' ELSE null END) AS "SQL",
       max(CASE WHEN course = 'UNIX' THEN '○' ELSE null END) AS "UNIX",
       max(CASE WHEN course = 'Java' THEN '○' ELSE null END) AS "Java"
 FROM Courses
GROUP BY name;
nameSQLUNIXJAVA
Bob(null)
Cherry(null)(null)
Eric(null)(null)
John(null)
Marry(null)(null)
  1. 如果要用字符或数的话,要确保true分支的字符(数)要大于else分支的字符(数),否则会覆盖。
SELECT name,
   max(CASE WHEN course = 'SQL' THEN 'b' ELSE 'a' END) AS "SQL",
   max(CASE WHEN course = 'UNIX' THEN 'b' ELSE 'a' END) AS "UNIX",
   max(CASE WHEN course = 'Java' THEN 'b' ELSE 'a' END) AS "Java"
FROM Courses
GROUP BY name;
  1. CASE WHEN嵌套 + SUM()
SELECT  name,
	CASE WHEN SUM(CASE WHEN course = 'SQL' THEN 1 ELSE 0 END) >= 1
			 THEN '○' ELSE 'x' END AS "SQL",
	CASE WHEN SUM(CASE WHEN course = 'UNIX' THEN 1 ELSE 0 END) >= 1
			 THEN '○' ELSE 'x' END AS "UNIX",
	CASE WHEN SUM(CASE WHEN course = 'Java' THEN 1 ELSE 0 END) >= 1
			 THEN '○' ELSE 'x' END AS "Java"
FROM Courses
GROUP BY name;
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值