1. 程式人生 > >MySQL 如何實現行轉列分級輸出?

MySQL 如何實現行轉列分級輸出?

概述

好久沒寫SQL語句,今天看到問答中的一個問題,拿來研究一下。

情景簡介

學校裡面記錄成績,每個人的選課不一樣,而且以後會新增課程,所以不需要把所有課程當作列。資料表裡面數據如下圖,使用姓名+課程作為聯合主鍵(有些需求可能不需要聯合主鍵)。本文以MySQL為基礎,其他資料庫會有些許語法不同。

資料庫表資料:


處理後的結果(行轉列):


方法一:

這裡可以使用Max,也可以使用Sum;

注意第二張圖,當有學生的某科成績缺失的時候,輸出結果為Null; 

SELECT
	SNAME,
	MAX(
		CASE CNAME
		WHEN 'JAVA' THEN
			SCORE
		END
	) JAVA,
	MAX(
		CASE CNAME
		WHEN 'mysql' THEN
			SCORE
		END
	) mysql
FROM
	stdscore
GROUP BY
	SNAME;

可以在第一個Case中加入Else語句解決這個問題:

SELECT
	SNAME,
	MAX(
		CASE CNAME
		WHEN 'JAVA' THEN
			SCORE
		ELSE
			0
		END
	) JAVA,
	MAX(
		CASE CNAME
		WHEN 'mysql' THEN
			SCORE
		ELSE
			0
		END
	) mysql
FROM
	stdscore
GROUP BY
	SNAME;

方法二:
SELECT DISTINCT  a.sname,
(SELECT score FROM stdscore b WHERE a.sname=b.sname AND b.CNAME='JAVA' ) AS 'JAVA',
(SELECT score FROM stdscore b WHERE a.sname=b.sname AND b.CNAME='mysql' ) AS 'mysql'
FROM stdscore a

方法三:

DROP PROCEDURE
IF EXISTS sp_score;
DELIMITER &&

CREATE PROCEDURE sp_score ()
BEGIN
	#課程名稱
	DECLARE
		cname_n VARCHAR (20) ; #所有課程數量
		DECLARE
			count INT ; #計數器
			DECLARE
				i INT DEFAULT 0 ; #拼接SQL字串
			SET @s = 'SELECT sname' ;
			SET count = (
				SELECT
					COUNT(DISTINCT cname)
				FROM
					stdscore
			) ;
			WHILE i < count DO


			SET cname_n = (
				SELECT
					cname
				FROM
					stdscore
				GROUP BY CNAME 
				LIMIT i,
				1
			) ;
			SET @s = CONCAT(
				@s,
				', SUM(CASE cname WHEN ',
				'\'',
				cname_n,
				'\'',
				' THEN score ELSE 0 END)',
				' AS ',
				'\'',
				cname_n,
				'\''
			) ;
			SET i = i + 1 ;
			END
			WHILE ;
			SET @s = CONCAT(
				@s,
				' FROM stdscore GROUP BY sname'
			) ; #用於除錯
			#SELECT @s;
			PREPARE stmt
			FROM
				@s ; EXECUTE stmt ;
			END&&

CALL sp_score () ;


處理後的結果(行轉列)分級輸出:


方法一:

這裡可以使用Max,也可以使用Sum;

注意第二張圖,當有學生的某科成績缺失的時候,輸出結果為Null; 

SELECT
	SNAME,
	MAX(
		CASE CNAME
		WHEN 'JAVA' THEN
			(
				CASE
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') > 20 THEN
					'優秀'
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') > 10 THEN
					'良好'
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') >= 0 THEN
					'普通'
				ELSE
					'較差'
				END
			)
		END
	) JAVA,
	MAX(
		CASE CNAME
		WHEN 'mysql' THEN
			(
				CASE
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') > 20 THEN
					'優秀'
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') > 10 THEN
					'良好'
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') >= 0 THEN
					'普通'
				ELSE
					'較差'
				END
			)
		END
	) mysql
FROM
	stdscore
GROUP BY
	SNAME;


方法二:

SELECT DISTINCT  a.sname,
(SELECT (
				CASE
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') > 20 THEN
					'優秀'
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') > 10 THEN
					'良好'
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') >= 0 THEN
					'普通'
				ELSE
					'較差'
				END
			) FROM stdscore b WHERE a.sname=b.sname AND b.CNAME='JAVA' ) AS 'JAVA',
(SELECT (
				CASE
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') > 20 THEN
					'優秀'
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') > 10 THEN
					'良好'
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME='JAVA') >= 0 THEN
					'普通'
				ELSE
					'較差'
				END
			) FROM stdscore b WHERE a.sname=b.sname AND b.CNAME='mysql' ) AS 'mysql'
FROM stdscore a
方法三:
DROP PROCEDURE
IF EXISTS sp_score;
DELIMITER &&

CREATE PROCEDURE sp_score ()
BEGIN
	#課程名稱
	DECLARE
		cname_n VARCHAR (20) ; #所有課程數量
		DECLARE
			count INT ; #計數器
			DECLARE
				i INT DEFAULT 0 ; #拼接SQL字串
			SET @s = 'SELECT sname' ;
			SET count = (
				SELECT
					COUNT(DISTINCT cname)
				FROM
					stdscore
			) ;
			WHILE i < count DO


			SET cname_n = (
				SELECT
					cname
				FROM
					stdscore
        GROUP BY CNAME 
				LIMIT i, 1
			) ;
			SET @s = CONCAT(
				@s,
				', MAX(CASE cname WHEN ',
				'\'',
				cname_n,
				'\'',
				' THEN (
				CASE
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME=\'',cname_n,'\') > 20 THEN
					\'優秀\'
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME=\'',cname_n,'\') > 10 THEN
					\'良好\'
				WHEN SCORE - (select avg(SCORE) from stdscore where CNAME=\'',cname_n,'\') >= 0 THEN
					\'普通\'
				ELSE
					\'較差\'
				END
			) END)',
				' AS ',
				'\'',
				cname_n,
				'\''
			) ;
			SET i = i + 1 ;
			END
			WHILE ;
			SET @s = CONCAT(
				@s,
				' FROM stdscore GROUP BY sname'
			) ; 
			#用於除錯
			#SELECT @s;
			PREPARE stmt
			FROM
				@s ; EXECUTE stmt ;
			END&&


CALL sp_score ();


幾種方法比較分析

第一種使用了分組,對每個課程分別處理。
第二種方法使用了表連線。
第三種使用了儲存過程,實際上可以是第一種或第二種方法的動態化,先計算出所有課程的數量,然後對每個分組進行課程查詢。這種方法的一個最大的好處是當新增了一門課程時,SQL語句不需要重寫。

小結

關於行轉列和列轉行

這個概念似乎容易弄混,有人把行轉列理解為列轉行,有人把列轉行理解為行轉列;

這裡做個定義:

行轉列:把表中特定列(如本文中的:CNAME)的資料去重後做為列名(如查詢結果行中的“JAVA,mysql”,處理後是做為列名輸出);

列轉行:可以說是行轉列的反轉,把表中特定列(如本文處理結果中的列名“JAVA,mysql”)做為每一行資料對應列“CNAME”的值;

關於效率

不知道有什麼好的生成模擬資料的方法或工具,麻煩小夥伴推薦一下,抽空我做一下對比;

還有其它更好的方法嗎?

本文使用的幾種方法應該都有優化的空間,特別是使用儲存過程的話會更加靈活,功能更強大;

本文的分級只是給出一種思路,分級的方法如果學生的成績相差較小的話將失去意義;

如果小夥伴有更好的方法,還請不吝賜教,感激不盡!

有些需求可能不需要聯合主鍵

有些需求可能不需要聯合主鍵,因為一門課程可能允許學生考多次,取最好的一次成績,或者取多次的平均成績。

相關推薦

MySQL 如何實現分級輸出

概述好久沒寫SQL語句,今天看到問答中的一個問題,拿來研究一下。情景簡介學校裡面記錄成績,每個人的選課不一樣,而且以後會新增課程,所以不需要把所有課程當作列。資料表裡面數據如下圖,使用姓名+課程作為聯合主鍵(有些需求可能不需要聯合主鍵)。本文以MySQL為基礎,其他資料庫會有

MySQL 實現SQL

概述 好久沒寫SQL語句,今天看到問答中的一個問題,拿來研究一下。 情景簡介 學校裡面記錄成績,每個人的選課不一樣,而且以後會新增課程,所以不需要把所有課程當作列。資料表裡面數據如下圖,使用姓名+課程作為聯合主鍵(有些需求可能不需要聯合主鍵)。本文以MySQL為基

MySQL中GROUP_CONCAT函式長度限制處理/實現的功能

MySQL提供了一個很方便的函式group_concat可以實現行轉列的功能。 SELECT t.*, (SELECT GROUP_CONCAT(fellow_company_id) v1 FROM t_trade_fellow a WHERE fellow_type = '1' AND a.company_

SQL語句實現

within sel 11g wm_concat 進行 com 10g 本地測試 from 最近在維護一個項目,出現了一下bug需要進行調試,於是把正式庫上面的代碼搬到本地庫上面,數據庫是本地的,跑項目的時候調試發現代碼裏面帶有wmsys.wm_concat函數的SQL語句

mySQL

因為MYSQL裡邊沒有  PIVOT      現記錄:   原表格:   mysql語句: SELECT MAX(CASE WHEN corol='紅' THEN NUM else

SQL資料庫查詢實現轉行結果SQL語句

 CREATETABLE[StudentScores](    [UserName]NVARCHAR(20),        --學生姓名[Subject]NVARCHAR(30),        --科目[Score]FLOAT,               --成績)INSERTINTO[StudentS

Mysql-sql

原始資料如下圖所示:(商品的銷售明細)date=業務日期;Item=商品名稱;saleqty=銷售數量 -- 建立測試資料(表)create table test (Date varchar(10), item char(10),saleqty int);insert test values('2010

SQL2005/2008中實現的2種方法

  CREATE TABLE sales.salesByMonth(year char(4),month char(3),amount money,PRIMARY KEY (year, month))INSERT INTO sales.salesByMonth (year,

mysql動態

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 DROP TABLE IF EXISTS

Oracle 11g Pivot函式實現

先上語法規範: SELECT .... FROM PIVOT ( aggregate-function() FOR IN (, ,..., )

MySql 動態整理

在開發過程中,我們或許經常碰到這樣的需求,即將某些sql查詢資料實現動態行轉列。 舉例來說:一個學生參加過多次考試,如果想知道該學生最近幾次考試語文的成績,如下圖: 對於使用者來說,我們希望看到的如下圖(即資料動態實現行轉列): 下面看資料表結構: CREAT

使用pivot和unpivot語句實現,轉行

在做報表時,經常需要將資料表中的行轉列,或者列轉行,如果不知道方法,你會覺得通過SQL語句來實現非常難。這裡,我將使用pivot和unpivot來實現看似複雜的功能。 引用MSDN: 可以使用 PIVOT 和 UNPIVOT 關係運算符將表值表示式更改為另一個表。PIVOT

mysql 動態

前言: mysql的行轉列並沒有mssql中的pivot 測試資料: DROP TABLE IF EXISTS `mytest`; CREATE TABLE `mytest` ( `id` int(11) NOT NULL AUTO_INCREMENT, `cl

SqlServer PIVOT函式快速實現,UNPIVOT實現轉行

我們在寫Sql語句的時候沒經常會遇到將查詢結果行轉列,列轉行的需求,拼接sql字串,然後使用sp_executesql執行sql字串是比較常規的一種做法。但是這樣做實現起來非常複雜,而在SqlServer2005中我們有了PIVOT/UNPIVOT函式可以快速實現行轉列和

如何優雅地使用R實現

網上有網友問到:在一個資料夾下,收集了幾個股票資料的檔案,對應的股票名稱為第2列,對應的時間為第3列,對應的收盤價為第10列。 現在想做到下圖所示的效果,也就是行為時間,列為對應的股票的收盤

Oracle SQL函式pivot、unpivot置函式實現轉行

函式PIVOT、UNPIVOT轉置函式實現行轉列、列轉行,效果如下圖所示: 1.PIVOT為行轉列,從圖示的左邊到右邊 2.UNPIVOT為列轉行,從圖示的右邊到左邊 3.左邊為縱表,結構簡單,易擴充套件 4.右邊為橫表,展示清晰,方便查詢 5.很多時候業務表為縱表,但是統

linq實現

nt maxcount = Model .SelectMany(h => h.DetailList).OrderBy(h => h.DetailType) .

LINQ TO SQL 實現

表結構如下: id NAME result ----------- -------------------- ------ 1 jim 勝 2

MySQL轉行、連線字串 concat、concat_ws、group_concat函式用法

1.concat函式 使用方法: CONCAT(str1,str2,…) 返回結果為連線引數產生的字串。如有任何一個引數為NULL ,則返回值為 NULL。 注意: 如果所有引數均為非二進位制字串,則結果為非二進位制字串。 如果自變

mysql簡單

DROP TABLE IF EXISTS `tb`; CREATE TABLE `tb` ( `id` varchar(12) CHARACTER SET utf8 COLLATE utf8_gene