【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()函數可以將所屬於同一分組的多個行轉化爲一個列返回,且分隔符可以自定義。

我們只實現了一個需求的查詢,其他兩個查詢情況類似(偷懶不想寫了),這裏不做過多贅述,有興趣的可以寫一下,歡迎提出指正意見

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章