SQL中進行轉列 在很多筆試的程式員中會有很多寫SQL的情況,其中很多時候會考察行轉列。那麼這個時候如果能寫出來幾種行轉列的SQL,會給面試官留下比較好的印象。 以下是這次sql轉換的表結構以及數據 數據準備 1、學生表 CREATE TABLE `student` ( `stuid` VARCHA ...
SQL中進行轉列
在很多筆試的程式員中會有很多寫SQL的情況,其中很多時候會考察行轉列。那麼這個時候如果能寫出來幾種行轉列的SQL,會給面試官留下比較好的印象。
以下是這次sql轉換的表結構以及數據
數據準備
1、學生表
CREATE TABLE `student` (
`stuid` VARCHAR(16) NOT NULL COMMENT '學號',
`stunm` VARCHAR(20) NOT NULL COMMENT '學生姓名',
PRIMARY KEY (`stuid`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
2、課程表
CREATE TABLE `curriculum` (
`courseno` VARCHAR(20) NOT NULL,
`coursenm` VARCHAR(100) NOT NULL,
PRIMARY KEY (`courseno`)
)
COMMENT='課程表'
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
3、成績表
CREATE TABLE `score` (
`stuid` VARCHAR(16) NOT NULL,
`courseno` VARCHAR(20) NOT NULL,
`scores` FLOAT NULL DEFAULT NULL,
PRIMARY KEY (`stuid`, `courseno`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
4、基本數據
/*學生表數據*/
Insert Into student (stuid, stunm) Values('1001', '張三');
Insert Into student (stuid, stunm) Values('1002', '李四');
Insert Into student (stuid, stunm) Values('1003', '趙二');
Insert Into student (stuid, stunm) Values('1004', '王五');
Insert Into student (stuid, stunm) Values('1005', '劉青');
Insert Into student (stuid, stunm) Values('1006', '周明');
/*課程表數據*/
Insert Into curriculum (courseno, coursenm) Values('C001', '大學語文');
Insert Into curriculum (courseno, coursenm) Values('C002', '新視野英語');
Insert Into curriculum (courseno, coursenm) Values('C003', '離散數學');
Insert Into curriculum (courseno, coursenm) Values('C004', '概率論與數理統計');
Insert Into curriculum (courseno, coursenm) Values('C005', '線性代數');
Insert Into curriculum (courseno, coursenm) Values('C006', '高等數學(一)');
Insert Into curriculum (courseno, coursenm) Values('C007', '高等數學(二)');
/*成績表數據*/
Insert Into number_result(stuid, courseno, scores) Values('1001', 'C001', 67);
Insert Into number_result(stuid, courseno, scores) Values('1002', 'C001', 68);
Insert Into number_result(stuid, courseno, scores) Values('1003', 'C001', 69);
Insert Into number_result(stuid, courseno, scores) Values('1004', 'C001', 70);
Insert Into number_result(stuid, courseno, scores) Values('1005', 'C001', 71);
Insert Into number_result(stuid, courseno, scores) Values('1006', 'C001', 72);
Insert Into number_result(stuid, courseno, scores) Values('1001', 'C002', 87);
Insert Into number_result(stuid, courseno, scores) Values('1002', 'C002', 88);
Insert Into number_result(stuid, courseno, scores) Values('1003', 'C002', 89);
Insert Into number_result(stuid, courseno, scores) Values('1004', 'C002', 90);
Insert Into number_result(stuid, courseno, scores) Values('1005', 'C002', 91);
Insert Into number_result(stuid, courseno, scores) Values('1006', 'C002', 92);
Insert Into number_result(stuid, courseno, scores) Values('1001', 'C003', 83);
Insert Into number_result(stuid, courseno, scores) Values('1002', 'C003', 84);
Insert Into number_result(stuid, courseno, scores) Values('1003', 'C003', 85);
Insert Into number_result(stuid, courseno, scores) Values('1004', 'C003', 86);
Insert Into number_result(stuid, courseno, scores) Values('1005', 'C003', 87);
Insert Into number_result(stuid, courseno, scores) Values('1006', 'C003', 88);
Insert Into number_result(stuid, courseno, scores) Values('1001', 'C004', 88);
Insert Into number_result(stuid, courseno, scores) Values('1002', 'C004', 89);
Insert Into number_result(stuid, courseno, scores) Values('1003', 'C004', 90);
Insert Into number_result(stuid, courseno, scores) Values('1004', 'C004', 91);
Insert Into number_result(stuid, courseno, scores) Values('1005', 'C004', 92);
Insert Into number_result(stuid, courseno, scores) Values('1006', 'C004', 93);
Insert Into number_result(stuid, courseno, scores) Values('1001', 'C005', 77);
Insert Into number_result(stuid, courseno, scores) Values('1002', 'C005', 78);
Insert Into number_result(stuid, courseno, scores) Values('1003', 'C005', 79);
Insert Into number_result(stuid, courseno, scores) Values('1004', 'C005', 80);
Insert Into number_result(stuid, courseno, scores) Values('1005', 'C005', 81);
Insert Into number_result(stuid, courseno, scores) Values('1006', 'C005', 82);
Insert Into number_result(stuid, courseno, scores) Values('1001', 'C006', 77);
Insert Into number_result(stuid, courseno, scores) Values('1002', 'C006', 78);
Insert Into number_result(stuid, courseno, scores) Values('1003', 'C006', 79);
Insert Into number_result(stuid, courseno, scores) Values('1004', 'C006', 80);
Insert Into number_result(stuid, courseno, scores) Values('1005', 'C006', 81);
Insert Into number_result(stuid, courseno, scores) Values('1006', 'C006', 82);
我們先看一下最基本的查詢效果是什麼樣的
靜態行轉列
Select st.stuid, st.stunm,
MAX(CASE c.coursenm WHEN '大學語文' THEN s.scores ELSE 0 END ) '大學語文',
MAX(CASE c.coursenm WHEN '新視野英語' THEN ifnull(s.scores,0) ELSE 0 END ) '新視野英語',
MAX(CASE c.coursenm WHEN '離散數學' THEN ifnull(s.scores,0) ELSE 0 END ) '離散數學',
MAX(CASE c.coursenm WHEN '概率論與數理統計' THEN ifnull(s.scores,0) ELSE 0 END ) '概率論與數理統計',
MAX(CASE c.coursenm WHEN '線性代數' THEN ifnull(s.scores,0) ELSE 0 END ) '線性代數',
MAX(CASE c.coursenm WHEN '高等數學(一)' THEN ifnull(s.scores,0) ELSE 0 END ) '高等數學(一)',
MAX(CASE c.coursenm WHEN '高等數學(二)' THEN ifnull(s.scores,0) ELSE 0 END ) '高等數學(二)'
From student st
Left Join number_result s On st.stuid = s.stuid
Left Join curriculum c On c.courseno = s.courseno
Group by st.stuid
很多人肯定不理解為什麼要使用Max函數,實際上大家都知道聚合函數是和分組進行搭配使用的。這一點毋庸置疑,那麼大家可以把Max函數去掉看看會顯示什麼效果。切記去掉Max函數記得把分組也去掉,這樣才能看到本質。
這時大家會發現沒列都出現了重覆的數據,而且只有一列是有值得。其他列都是0.那麼這個時候就應該能很清楚的認識到,為什麼使用Max函數了。在分組的同時取一組中的最大值。
靜態行轉列有一個弊端就是第一確定有多少個課程,然後再把課程名稱拿出來再寫查詢語句。但是這樣會寫很多東西。
動態行轉列
首先我們要動態的獲取是列的數據 :
MAX(CASE c.coursenm WHEN ‘大學語文’ THEN s.scores ELSE 0 END ) ‘大學語文’,
MAX(CASE c.coursenm WHEN ‘線性代數’ THEN ifnull(s.scores,0) ELSE 0 END ) ‘線性代數’,
MAX(CASE c.coursenm WHEN ‘離散數學’ THEN ifnull(s.scores,0) ELSE 0 END ) ‘離散數學’
這裡想動態的獲取到上面的就需要拼接sql列 :
SELECT
GROUP_CONCAT( DISTINCT CONCAT( ‘MAX(IF(c.coursenm = ‘’’, c.coursenm, ‘’’, s.scores, 0)) AS ‘’’, c.coursenm, ‘’’’ ) )
FROM
curriculum c;
在這裡解釋一下 :
concat()函數 : 將多個字元串連接成一個字元串。
語法:concat_ws(separator, str1, str2, …)
說明:第一個參數指定分隔符。需要註意的是分隔符不能為null,如果為null,則返回結果為null。
group_concat()函數 :將group by產生的同一個分組中的值連接起來,返回一個字元串結果。
語法:group_concat( [distinct] 要連接的欄位 [order by 排序欄位 asc/desc ] [separator ‘分隔符’] )。
說明:通過使用distinct可以排除重覆值;如果希望對結果中的值進行排序,可以使用order by子句;separator是一個字元串值,預設為一個逗號。
動態的列是拿到了,那如何再結合SQL語句進行查詢得到結果呢?
這裡要說明一點,因為用到了拼接函數,如果像上面的查詢語句,只是把那幾行語句替換掉,也就是下麵這樣
Select st.stuid, st.stunm,
(
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(c.coursenm = ''',
c.coursenm,
''', s.scores, NULL)) AS ',
c.coursenm
)
)
FROM curriculum c
)
From Student st
Left Join number_result s On st.stuid = s.stuid
Left Join curriculum c On c.courseno = s.courseno
Group by st.stuid;
最終結果如下 :
SET @SQL = NULL;
SELECT
GROUP_CONCAT( DISTINCT CONCAT( ‘MAX(IF(c.coursenm = ‘’’, c.coursenm, ‘’’, s.scores, 0)) AS ‘’’, c.coursenm, ‘’’’ ) ) INTO @SQL
FROM
curriculum c;
SET @SQL = CONCAT( 'Select st.stuid, st.stunm, ‘, @SQL, ’ From Student st
Left Join number_result s On st.stuid = s.stuid
Left Join curriculum c On c.courseno = s.courseno
Group by st.stuid’ );
PREPARE stmt
FROM
@SQL;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
這裡簡單說一下,先生命一個變數賦值為null,把拼接的查詢sql賦值給聲明的變數表中,也可以理解為生成一個臨時表,把查詢出來的數據放到臨時表中。預定義一個語句,並將它賦給 stmt。
存儲過程–動態行轉列
用存儲過程的好處是,方便我們調用,相當於一個函數,其他可能也是類似的查詢不需再重覆寫代碼,直接調存儲過程就好,還能隨心所欲的加上if條件判斷。創建存儲過程的語句我就不多寫了,這裡把上面的查詢語句直接放到創建存儲過程的begin和end直接就可以了,如下:
DELIMITER &&
drop procedure if exists SP_QueryData;
Create Procedure SP_QueryData(IN stuid varchar(16))
READS SQL DATA
BEGIN
SET @sql = NULL;
SET @stuid = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(c.coursenm = ''',
c.coursenm,
''', s.scores, 0)) AS ''',
c.coursenm, '''
)
) INTO @sql
FROM curriculum c;
SET @sql = CONCAT('Select st.stuid, st.stunm, ', @sql,
' From student st
Left Join number_result s On st.stuid = s.stuid
Left Join curriculum c On c.courseno = s.courseno');
IF stuid is not null and stuid <> '' then
SET @stuid = stuid;
SET @sql = CONCAT(@sql, ' Where st.stuid = '', @stuid, ''');
END IF;
SET @sql = CONCAT(@sql, ' Group by st.stuid');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END &&
DELIMITER ;