【MySQL】Mysql统计之行转列

事出必有因

几年前一直是做报表相关的业务,自认为对SQL和统计相关的知识还是比较熟练吧,昨天一妹子问我一个统计SQL让我把多行数据汇总转成列来展示,我寻思着这不就是个简单的行转列的问题嘛,上手就写,可是…尴尬了 执行出来的数据不是想要的效果,好在最后我急中生智解决了这个问题不至于颜面扫地,为了避免类似的问题再发生,所以把出来方式新记录一下。

举个栗子

我这里就拿消费者(customer)、订单(order)、商品(product)这个简单的场景来举例子了,首先我们做一下准备工作,先把这几张表创建一下并插入一些测试数据:

#消费者表
DROP TABLE IF EXISTS customer ;
CREATE TABLE customer (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `name` varchar(24) NOT NULL DEFAULT '' COMMENT '名称',
  `nick_name` varchar(24) NOT NULL DEFAULT '' COMMENT '昵称',
  `create_at` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
)COMMENT='消费者表';

#插入测试数据
INSERT INTO customer(name,nick_name,create_at)
VALUES
("老王","LW",now()),
("老张","LZ",now()),
("老李","LL",now()),
("老陈","LC",now()),
("老不死","LBS",now())
;

#商品表
DROP TABLE IF EXISTS product ;
CREATE TABLE product (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `name` varchar(24) NOT NULL DEFAULT '' COMMENT '商品名称',
  `price` decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '商品单价',
  `create_at` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
)COMMENT='商品表';
#插入测试数据
INSERT INTO product(name,price,create_at)
VALUES
("java从入门到放弃",100.0,now()),
("MySql花式删库指南",150.00,now()),
("程序员装逼指南",300.00,now()),
("颈椎病康复指南",20,now())
;

#订单表
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `customer_id` int(11) NOT NULL DEFAULT 0 COMMENT '消费者ID',
  `product_id` int(11) NOT NULL DEFAULT 0 COMMENT '商品id',
  `order_no` varchar(32) NOT NULL DEFAULT '' COMMENT '订单号',
  `product_cnt` int(11) NOT NULL DEFAULT 0 COMMENT '商品数量',
	`product_price` decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '商品单价',
  `amount` decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '订单金额',
  `create_at` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
)COMMENT='订单表';

#插入测试数据
INSERT INTO orders(customer_id,product_id,order_no,product_cnt,product_price,amount,create_at)
VALUES
(1,1,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),2,100.0,2*100,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(2,1,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),3,100.0,3*100,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(3,1,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),1,100.0,1*100,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(4,1,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),5,100.0,5*100,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(1,2,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),7,150.0,7*150,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(2,2,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),5,150.0,5*150,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(3,2,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),8,150.0,8*150,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(4,2,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),3,150.0,3*150,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(1,3,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),9,300.0,9*300,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(2,3,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),6,300.0,6*300,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(3,3,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),34,300.0,34*300,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(4,3,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),6,300.0,6*300,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(1,4,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),4,20.0,4*20,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(2,4,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),23,20.0,23*20,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(3,4,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),12,20.0,12*20,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY)),
(4,4,CONCAT(unix_timestamp(now()),CEILING(RAND()*10000+100000)),1,20.0,1*20,DATE_ADD(now(),INTERVAL FLOOR(RAND()*-100) DAY))
;

主备工作已经就绪,接下来我们先看一下我们的需求:

  1. 查询所有用户近一年每个月的购物情况
  2. 查询所有产品的销售情况,按月统计
  3. 查询所有用户近一年所购产品的情况,按月统计

我们依次对以上需求进行分析,一步一步来解决问题
查询所有用户近一年每个月的购物情况
首先我们查询用户近一年的购物情况,SQL如下:

SELECT
	c.id AS "消费者ID",
	c.`name` AS "消费者姓名",
	DATE_FORMAT(o.create_at, '%Y-%m') as "消费日期",
	SUM(o.amount) as "消费总金额"
FROM
	customer c
INNER JOIN orders o ON c.id = o.customer_id
GROUP BY
	c.id,
	c. NAME,
	DATE_FORMAT(o.create_at, '%Y-%m')

查询结果如图:
在这里插入图片描述
我们观察上面的数据发现 有消费的日期只有 2019-08,2019-09,2019-10,2019-11这几个日期,那么我们如何将“消费日期”这一列的每一行数据转成列呢?也就是统计出每个月所有用户的消费情况,Oracle有现成的行转列的函数,但是mysql 貌似就要走曲线救国的道路了,我第一个想到的就是case…when 函数来解决,于是有了下面的SQL:

查询方式一

SELECT
tmp.`消费者ID`,tmp.`消费者姓名`,
SUM(CASE tmp.`消费日期` WHEN '2019-08' THEN tmp.`订单总金额` ELSE 0 END) as "2019-08",
SUM(CASE tmp.`消费日期` WHEN '2019-09' THEN tmp.`订单总金额` ELSE 0 END) as "2019-09",
SUM(CASE tmp.`消费日期` WHEN '2019-10' THEN tmp.`订单总金额` ELSE 0 END) as "2019-10",
SUM(CASE tmp.`消费日期` WHEN '2019-11' THEN tmp.`订单总金额` ELSE 0 END) as "2019-11"
from (
SELECT
	c.id AS "消费者ID",
	c.`name` AS "消费者姓名",
	DATE_FORMAT(o.create_at, '%Y-%m') as "消费日期",
	SUM(o.amount) as "订单总金额"
FROM
	customer c
INNER JOIN orders o ON c.id = o.customer_id
GROUP BY
	c.id,c. NAME,DATE_FORMAT(o.create_at, '%Y-%m')
) tmp GROUP BY tmp.`消费者ID`;

统计每个月用户的消费情况结果是出来了,看结果已经达到我们想要的样子


查询方式二
虽然 case…when 的方式可以完成我们的需求,但是子查询貌似看着有点烦,想想其他方式,貌似IF(expr1,expr2,expr3)函数也可以做到类似的事情,我们对以上SQL进行改造:


SELECT
	c.id AS "消费者ID",
	c.`name` AS "消费者姓名",
	sum(IF(DATE_FORMAT(o.create_at, '%Y-%m')='2019-08',o.amount,0.00)) as "2019-08",
	sum(IF(DATE_FORMAT(o.create_at, '%Y-%m')='2019-09',o.amount,0.00)) as "2019-09",
	sum(IF(DATE_FORMAT(o.create_at, '%Y-%m')='2019-10',o.amount,0.00)) as "2019-10",
	sum(IF(DATE_FORMAT(o.create_at, '%Y-%m')='2019-11',o.amount,0.00)) as "2019-11"
FROM
	customer c
INNER JOIN orders o ON c.id = o.customer_id
GROUP BY
	c.id,c.`name`;

查询结果:
在这里插入图片描述
对比上面第一种方法,查询结果一致。


查询方式三
以上查询方式看着SQL都很复杂,适用于直接从数据库中导出所用数据(前提是数据量不大,如果数据量太大,要注意索引的问题),还有一个函数可以将多行数据转成一行,GROUP_CONCAT() 函数,具体我们看怎么用:

select 
tmp.`消费者ID`,tmp.`消费者姓名`,
GROUP_CONCAT(tmp.`消费日期`,' 消费金额为:',tmp.`订单总金额`)
from (
SELECT
	c.id AS "消费者ID",
	c.`name` AS "消费者姓名",
	DATE_FORMAT(o.create_at, '%Y-%m') as "消费日期",
	SUM(o.amount) as "订单总金额"
FROM
	customer c
INNER JOIN orders o ON c.id = o.customer_id
GROUP BY
	c.id,c.NAME,DATE_FORMAT(o.create_at, '%Y-%m'))tmp GROUP BY tmp.`消费者ID`

查询结果:
在这里插入图片描述GROUP_CONCAT()函数可以将所属于同一分组的多个行转化为一个列返回,且分隔符可以自定义。

我们只实现了一个需求的查询,其他两个查询情况类似(偷懒不想写了),这里不做过多赘述,有兴趣的可以写一下,欢迎提出指正意见

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农小李子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值