SQL row and column conversion is used more frequently, and there are often such topics in interviews. Several common methods of mysql and sql server are summarized. Please correct me if there are any mistakes.
MYSQL row to column
CREATE TABLE `tbl_grade` ( `id` bigint(10) NOT NULL AUTO_INCREMENT, `name` varchar(10) DEFAULT NULL, `course` varchar(10) DEFAULT NULL, `score` decimal(5,0) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO tbl_grade (`name`, `course`, `score`) VALUES ('Zhang San', 'language', '74'); INSERT INTO tbl_grade (`name`, `course`, `score`) VALUES ('Zhangsan', 'Math', '55'); INSERT INTO tbl_grade (`name`, `course`, `score`) VALUES ('Zhangsan', 'English', '66'); INSERT INTO tbl_grade (`name`, `course`, `score`) VALUES ('Li Si', 'language', '88'); INSERT INTO tbl_grade (`name`, `course`, `score`) VALUES ('Li Si', 'Math', '76'); INSERT INTO tbl_grade (`name`, `course`, `score`) VALUES ('Li Si', 'English', '69'); INSERT INTO tbl_grade (`name`, `course`, `score`) VALUES ('Wang Wu', 'language', '11'); INSERT INTO tbl_grade (`name`, `course`, `score`) VALUES ('Wang Wu', 'Math', '45'); INSERT INTO tbl_grade (`name`, `course`, `score`) VALUES ('Wang Wu', 'English', '68');
Method 1: Use table links
SELECT DISTINCT a.name, (SELECT score FROM tbl_grade b WHERE a.name = b.name AND b.course = 'language') AS 'language', (SELECT score FROM tbl_grade b WHERE a.name = b.name AND b.course = 'math') AS 'math', (SELECT score FROM tbl_grade b WHERE a.name = b.name AND b.course = 'English') AS 'English', SUM(score) AS 'total score', cast(avg(score*1.0) as decimal(18,2)) as 'average score' FROM tbl_grade a GROUP BY a.name
Method 2: Use grouping and process each group separately
SELECT DISTINCT a.name, SUM(CASE course WHEN 'language' THEN score END) AS 'language', SUM(CASE course WHEN 'Math' THEN score END) AS 'Math', SUM(CASE course WHEN 'English' THEN score END) AS 'English', SUM(score) AS 'total score', cast(avg(score*1.0) as decimal(18,2)) as 'average score' FROM tbl_grade a GROUP BY a.name
You can also replace case when then with if
SELECT DISTINCT a.name, SUM(IF (course = 'language' , score , null ) ) as 'language', SUM(IF (course = 'math' , score , null ) ) as 'math', SUM(IF (course = 'English' , score , null ) ) as 'English', SUM(score) AS 'total score', cast(avg(score*1.0) as decimal(18,2)) as 'average score' FROM tbl_grade a GROUP BY a.name
Method 4: The stored procedure is used, which is actually the dynamization of the second method. First, the number of all courses is calculated, and then the course query is performed for each group.
Obviously, the first two methods are hard-coded, and the SQL needs to be modified after adding courses. The third does not need to modify the SQL.
#You cannot delete a stored procedure in another stored procedure in MySQL #DROP PROCEDURE IF EXISTS sp_count(); DELIMITER && CREATE PROCEDURE sp_count() BEGIN #Course Title DECLARE course_name VARCHAR(20); #Number of all courses DECLARE count INT; #counter DECLARE i INT DEFAULT 0; # Concatenate SQL strings SET count = (SELECT COUNT(distinct course) FROM tbl_grade); SET @s = 'SELECT name'; WHILE i < count DO SET course_name = (SELECT course FROM tbl_grade LIMIT i,1); SET @s = CONCAT(@s,',SUM(CASE course WHEN ','\'',course_name,'\'',' THEN score END )',' AS ','\'',course_name,'\''); SET i = i+1; END WHILE; SET @s = CONCAT(@s,',sum(score) as total score,cast(avg(score*1.0) as decimal(18,2)) as average score FROM tbl_grade GROUP BY name'); PREPARE stmt FROM @s; EXECUTE stmt; END && call sp_count();
The results of the above methods are the same as shown in the figure below
MYSQL column to row
CREATE TABLE tbl_course( `id` BIGINT(10) not NULL AUTO_INCREMENT, `name` VARCHAR(10) DEFAULT NULL, `language` DECIMAL(5,0) DEFAULT null, `math` DECIMAL(5,0) DEFAULT null, `English` DECIMAL(5,0) DEFAULT null, PRIMARY KEY (`id`) ) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET=utf8
INSERT INTO tbl_course (`name`,`Chinese`,`math`,`English`) VALUES ('Zhang San','77','44','22'); INSERT INTO tbl_course (`name`,`Chinese`,`math`,`English`) VALUES ('Li Si','55','12','85'); INSERT INTO tbl_course (`name`,`Chinese`,`math`,`English`) VALUES ('Wang Wu','85','45','96');
Method 1: Manually enumerate the columns, then union the data
SELECT tepTable.* FROM ( SELECT name, "language", language as score FROM tbl_course union all SELECT name, "math", math as score FROM tbl_course union all SELECT name, "English", English as score FROM tbl_course ) tepTable ORDER BY tepTable.`name`
Method 2: Use a stored procedure to obtain the latest table fields of the tablefrom MySQL schema , and splicing SQL. In fact, it is a dynamic extension of method one.
DELIMITER $$ CREATE PROCEDURE sp_trans() BEGIN #Course Title DECLARE course_n VARCHAR(20); #Number of all courses DECLARE count INT; #counter DECLARE i INT DEFAULT 0; # Concatenate SQL strings SET @s = 'SELECT * FROM ('; SET count = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA ='test' AND TABLE_NAME='tbl_course' AND COLUMN_NAME <> 'name' AND COLUMN_NAME <> 'id'); WHILE i < count DO SET course_n = (SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA ='test' AND TABLE_NAME='tbl_course' AND COLUMN_NAME <> 'name' AND COLUMN_NAME <> 'id' LIMIT i,1); SET @s = CONCAT(@s, ' SELECT name, "'); SET @s = CONCAT(@s, course_n); SET @s = CONCAT(@s, '", '); SET @s = CONCAT(@s, course_n); SET @s = CONCAT(@s,' as score FROM tbl_course '); IF i <> count-1 THEN SET @s = CONCAT(@s, 'union all'); END IF; SET i = i+1; END WHILE; SET @s = CONCAT(@s, ' ) tepTable ORDER BY tepTable.`name`'); PREPARE stmt FROM @s; EXECUTE stmt; END $$ CALL sp_trans
The query results of the two methods are the same, as shown in the following figure
MSSQL row to column
if exists (select * from sysobjects where id = object_id('tbl_grade') and type = 'U') drop table tbl_grade go CREATE TABLE tbl_grade ( id bigint NOT NULL identity, name varchar(10) DEFAULT NULL, course varchar(10) DEFAULT NULL, score decimal(5,0) DEFAULT NULL, PRIMARY KEY (id) ) insert into tbl_grade VALUES ('Zhangsan','language',74) insert into tbl_grade VALUES ('Zhangsan','Math',83) insert into tbl_grade VALUES ('Zhangsan','English',93) insert into tbl_grade VALUES ('Li Si','language',74) insert into tbl_grade VALUES ('Li Si','Math',84) insert into tbl_grade VALUES ('Li Si','English',94) insert into tbl_grade VALUES ('Wang Wu','language',54) insert into tbl_grade VALUES ('Wang Wu','Math',63) insert into tbl_grade VALUES ('Wang Wu','English',72) SELECT * FROM tbl_grade
SELECT name,SUM(language) AS Chinese,SUM(math) AS math,SUM(English) AS English FROM tbl_grade g pivot( MAX(score) FOR course IN (Chinese, Math, English)) a GROUP BY name
MSSQL column to row
if exists (select * from sysobjects where id = object_id('tbl_grade') and type = 'U') drop table tbl_grade go CREATE TABLE tbl_grade ( id bigint NOT NULL identity, name varchar(10) DEFAULT NULL, language decimal(5,0) DEFAULT NULL, math decimal(5,0) DEFAULT NULL, English decimal(5,0) DEFAULT NULL, PRIMARY KEY (id) ) INSERT INTO tbl_grade (name, Chinese, math, English) VALUES ('Zhang San', 99, 44, 22) INSERT INTO tbl_grade (name, Chinese, math, English) VALUES ('Li Si', 55, 12, 85) INSERT INTO tbl_grade (name, Chinese, math, English) VALUES ('Wang Wu', 85, 45, 96)
Method 1: Static union all
SELECT * FROM ( SELECT name,course='language',score = language FROM tbl_grade UNION ALL SELECT name,course='math',score = math FROM tbl_grade UNION ALL SELECT name,course='English',score = English FROM tbl_grade ) t ORDER BY name,CASE course WHEN '语文' THEN 1 WHEN '数学' THEN 2 WHEN '英语' THEN 3 endMethod 2: Obtaining the column name dynamically is the dynamization of method 1. The quotename method is used here, which is the escape identifier.
DECLARE @sql VARCHAR(8000) SELECT @sql = isnull(@sql + ' union all ','') + ' select name, [course]=' + quotename(Name,'''') + ' , [score] = ' + quotename(Name) + ' from tbl_grade' FROM syscolumns WHERE Name != 'name' AND Name != 'id' AND ID = object_id('tbl_grade') exec(@sql + ' order by name') go
Method 3: Use unpivot
SELECT name,course,score FROM tbl_grade unpivot (score FOR course IN(Chinese, Math, English)) t
Method 4: Dynamic using unpivot
DECLARE @sql NVARCHAR(4000) SELECT @sql = isnull(@sql + ',','') + quotename(Name) FROM syscolumns WHERE ID = object_id('tbl_grade') AND Name NOT IN ('name','id') SET @sql = 'select name,[course],[score] from tbl_grade unpivot ([score] for [course] in(' + @sql + ')) b' exec(@sql)
The results of the above four methods are the same, as shown in the following figure