首页 > 数据库技术 > 详细

SQL进阶-行转列&列转行

时间:2019-09-04 13:35:30      阅读:55      评论:0      收藏:0      [点我收藏+]

一、行转列

1、建表

CREATE TABLE score(
    student_id VARCHAR(20) NOT NULL COMMENT 学生编号 DEFAULT ‘‘,
    student_name VARCHAR(50) NOT NULL COMMENT 学生姓名 DEFAULT ‘‘,
    gender VARCHAR(10) NOT NULL COMMENT 学生性别 DEFAULT ‘‘,
    subject_name VARCHAR(50) NOT NULL COMMENT 课程名称 DEFAULT ‘‘,
    score INTEGER NOT NULL COMMENT 分数 DEFAULT 0
)ENGINE=MyISAM DEFAULT CHARSET=gbk COMMENT=学生成绩;

DELETE FROM score;

INSERT INTO score VALUES(S001,张三,,高等数学,82);
INSERT INTO score VALUES(S001,张三,,计算机导论,67);
INSERT INTO score VALUES(S001,张三,,概率论,90);
INSERT INTO score VALUES(S001,张三,,机械原理,82);
INSERT INTO score VALUES(S002,李四,,高等数学,78);
INSERT INTO score VALUES(S002,李四,,计算机导论,76);
INSERT INTO score VALUES(S002,李四,,概率论,65);
INSERT INTO score VALUES(S002,李四,,几何学,43);
INSERT INTO score VALUES(S003,王五,,计算机导论,88);
INSERT INTO score VALUES(S003,王五,,概率论,98);
INSERT INTO score VALUES(S003,王五,,几何学,85);
INSERT INTO score VALUES(S004,赵六,,高等数学,84);
INSERT INTO score VALUES(S004,赵六,,计算机导论,76);
INSERT INTO score VALUES(S004,赵六,,机械原理,65);
INSERT INTO score VALUES(S004,赵六,,几何学,48);
INSERT INTO score VALUES(S005,孙七,,高等数学,34);
INSERT INTO score VALUES(S005,孙七,,计算机导论,91);
INSERT INTO score VALUES(S005,孙七,,概率论,82);
INSERT INTO score VALUES(S005,孙七,,机械原理,56);
INSERT INTO score VALUES(S005,孙七,,几何学,70);


2、利用max(CASE ... WHEN ... THEN .. ELSE END) AS ""的方式来实现

##利用max(CASE ... WHEN ... THEN .. ELSE END) AS 的方式来实现
##判断如果是这门学科,就取它的成绩,否则赋值为0,然后在成绩与0里取最大值

SELECT 
  student_id,
  student_name,
  MAX(CASE WHEN subject_name = 高等数学 THEN score ELSE 0 END) AS 高等数学,
  MAX(CASE WHEN subject_name = 计算机导论 THEN score ELSE 0 END) AS 计算机导论,
  MAX(CASE WHEN subject_name = 概率论 THEN score ELSE 0 END) AS 概率论,
  MAX(CASE WHEN subject_name = 机械原理 THEN score ELSE 0 END) AS 机械原理,
  MAX(CASE WHEN subject_name = 几何学 THEN score ELSE 0 END) AS 几何学 FROM score
GROUP BY 
  student_id,
  student_name
ORDER BY 
  student_id,
  student_name;


3、求男女生各科平均成绩

##平均成绩肯定是总分除以人数,但是呢,有的学生没有某一门学科的成绩,我们把它变成0,
##变成0其实是不妥当的,因为变成0,在做除法的时候,也会算成一个人,这样除的结果就会不准确
##所以应该把0变成null

SELECT 
  gender,
  AVG(CASE WHEN subject_name = 高等数学 THEN score ELSE NULL END) AS 高等数学,
  AVG(CASE WHEN subject_name = 计算机导论 THEN score ELSE NULL END) AS 计算机导论,
  AVG(CASE WHEN subject_name = 概率论 THEN score ELSE NULL END) AS 概率论,
  AVG(CASE WHEN subject_name = 机械原理 THEN score ELSE NULL END) AS 机械原理,
  AVG(CASE WHEN subject_name = 几何学 THEN score ELSE NULL END) AS 几何学 
FROM score
GROUP BY 
  gender
ORDER BY 
  gender;



总结:
    行转列,分组(GROUP BY)的列必须是除需要行转列之外的业务主键。
    例如tb表中业务主键应该是cname和cource,但是cource需要进行行转列,所以需要按照cname分组。


4、子查询的方式,求男女生各科成绩平均值

##

SELECT
  gender,
   MAX(CASE WHEN subject_name = 高等数学 THEN score_avg ELSE 0 END) AS 高等数学,
  MAX(CASE WHEN subject_name = 计算机导论 THEN score_avg ELSE 0 END) AS 计算机导论,
  MAX(CASE WHEN subject_name = 概率论 THEN score_avg ELSE 0 END) AS 概率论,
  MAX(CASE WHEN subject_name = 机械原理 THEN score_avg ELSE 0 END) AS 机械原理,
  MAX(CASE WHEN subject_name = 几何学 THEN score_avg ELSE 0 END) AS 几何学 
FROM
(
SELECT 
  gender,
  subject_name,
  AVG(score) AS score_avg
FROM score
GROUP BY gender,subject_name
)a
GROUP BY gender
ORDER BY gender;


5、GROUP_CONCAT

GROUP_CONCAT():该函数返回带有来自一个分组的连接的非NULL值的字符串结果。


SELECT
student_id,
student_name,
GROUP_CONCAT(subject_name) AS 课程名称, GROUP_CONCAT(score) AS 分数
FROM score
GROUP BY 
student_id,
student_name;

结果:

技术分享图片


二、列转行

1、建表

###
employee_sales表:

CREATE TABLE employee_sales(
    employee_id VARCHAR(20) NOT NULL COMMENT 员工编号 DEFAULT ‘‘,
    employee_name VARCHAR(50) NOT NULL COMMENT 员工姓名 DEFAULT ‘‘,
    sales_amount_q1  DECIMAL(18,2) NOT NULL COMMENT 一季度销售金额 DEFAULT 0,
    sales_amount_q2  DECIMAL(18,2) NOT NULL COMMENT 二季度销售金额 DEFAULT 0,
    sales_amount_q3  DECIMAL(18,2) NOT NULL COMMENT 三季度销售金额 DEFAULT 0,
    sales_amount_q4  DECIMAL(18,2) NOT NULL COMMENT 四季度销售金额 DEFAULT 0
)ENGINE=MyISAM DEFAULT CHARSET=gbk COMMENT=员工销售表;

INSERT INTO employee_sales VALUES(S001,张三,273193.41,84149.96,711798.18,847930.56);
INSERT INTO employee_sales VALUES(S002,李四,861586.57,819398.89,496788.47,1060.11);
INSERT INTO employee_sales VALUES(S003,王五,943899.97,793446.45,351139,640459.72);
INSERT INTO employee_sales VALUES(S004,赵六,585158.08,493034.64,576246.61,188906.14);



###
employee_assessment表:

CREATE TABLE employee_assessment(
    employee_id VARCHAR(20) NOT NULL COMMENT 员工编号 DEFAULT ‘‘,
    employee_name VARCHAR(50) NOT NULL COMMENT 员工姓名 DEFAULT ‘‘,
    assessment_level CHAR(4) NOT NULL COMMENT 季度考核等级 DEFAULT ‘‘,
    sales_amount  VARCHAR(500) NOT NULL COMMENT 销售金额 DEFAULT ‘‘
)ENGINE=MyISAM DEFAULT CHARSET=gbk COMMENT=员工考核表;

INSERT INTO employee_assessment VALUES(S001,张三,AABC,273193.41,84149.96,711798.18,847930.56);
INSERT INTO employee_assessment VALUES(S002,李四,CBAB,861586.57,819398.89,496788.47,1060.11);
INSERT INTO employee_assessment VALUES(S003,王五,BBAC,943899.97,793446.45,351139,640459.72);
INSERT INTO employee_assessment VALUES(S004,赵六,BACA,585158.08,493034.64,576246.61,188906.14);


2、UNION ALL 列转行

##union all 合并多个子集
##加了一个季度字段,方便查看
select employee_id,employee_name,一季度 as 季度,sales_amount_q1 from employee_sales
union all
select employee_id,employee_name,二季度 as 季度,sales_amount_q2 from employee_sales
UNION ALL
SELECT employee_id,employee_name,三季度 as 季度,sales_amount_q3 from employee_sales
UNION ALL
select employee_id,employee_name,四季度 as 季度,sales_amount_q4 from employee_sales
ORDER BY employee_id;


3、列转行2

##
先看employee_assessment表数据:
+-------------+---------------+------------------+-----------------------------------------+
| employee_id | employee_name | assessment_level | sales_amount                            |
+-------------+---------------+------------------+-----------------------------------------+
| S001        | 张三          | AABC             | 273193.41,84149.96,711798.18,847930.56  |
| S002        | 李四          | CBAB             | 861586.57,819398.89,496788.47,1060.11   |
| S003        | 王五          | BBAC             | 943899.97,793446.45,351139,640459.72    |
| S004        | 赵六          | BACA             | 585158.08,493034.64,576246.61,188906.14 |
+-------------+---------------+------------------+-----------------------------------------+


##
用union all列转行:
select employee_id,employee_name,assessment_level,1 as 季度,right(left(assessment_level,1),1) from employee_assessment union all
select employee_id,employee_name,assessment_level,2 as 季度,right(left(assessment_level,2),1) from employee_assessment union all
select employee_id,employee_name,assessment_level,3 as 季度,right(left(assessment_level,3),1) from employee_assessment union all
select employee_id,employee_name,assessment_level,4 as 季度,right(left(assessment_level,4),1) from employee_assessment;

结果:
+-------------+---------------+------------------+--------+-----------------------------------+
| employee_id | employee_name | assessment_level | 季度   | right(left(assessment_level,1),1) |
+-------------+---------------+------------------+--------+-----------------------------------+
| S001        | 张三          | AABC             | 1      | A                                 |
| S002        | 李四          | CBAB             | 1      | C                                 |
| S003        | 王五          | BBAC             | 1      | B                                 |
| S004        | 赵六          | BACA             | 1      | B                                 |
| S001        | 张三          | AABC             | 2      | A                                 |
| S002        | 李四          | CBAB             | 2      | B                                 |
| S003        | 王五          | BBAC             | 2      | B                                 |
| S004        | 赵六          | BACA             | 2      | A                                 |
| S001        | 张三          | AABC             | 3      | B                                 |
| S002        | 李四          | CBAB             | 3      | A                                 |
| S003        | 王五          | BBAC             | 3      | A                                 |
| S004        | 赵六          | BACA             | 3      | C                                 |
| S001        | 张三          | AABC             | 4      | C                                 |
| S002        | 李四          | CBAB             | 4      | B                                 |
| S003        | 王五          | BBAC             | 4      | C                                 |
| S004        | 赵六          | BACA             | 4      | A                                 |
+-------------+---------------+------------------+--------+-----------------------------------+


上面用union all虽然可以得到结果,但是当数据多的时候,就要写很多行union all,不太好?


可以用两个表的笛卡尔积:

##
建sequence 表:
此表中的数据要和要拆分的字段数相同:

CREATE TABLE sequence (seq_num INT);

INSERT INTO sequence
VALUES
    (1),
    (2),
    (3),
    (4);


##
right和left函数组合使用,获取某一位字符:
加where条件是因为,现在assessment_level字段正好和sequence表中的字段数相等,但是当两个字段数不等时,比如assessment_level只有三个,
加上where就可以起过滤作用,如:length(assessment_level) = 3,3已经不>=seq_num了,就算seq_num中有4,也不会select出来;

select employee_id,employee_name,assessment_level,seq_num as 季度,right(left(assessment_level,seq_num),1)
from employee_assessment,sequence where length(assessment_level) >= seq_num;

结果:
+-------------+---------------+------------------+--------+-----------------------------------------+
| employee_id | employee_name | assessment_level | 季度   | right(left(assessment_level,seq_num),1) |
+-------------+---------------+------------------+--------+-----------------------------------------+
| S001        | 张三          | AABC             |      1 | A                                       |
| S001        | 张三          | AABC             |      2 | A                                       |
| S001        | 张三          | AABC             |      3 | B                                       |
| S001        | 张三          | AABC             |      4 | C                                       |
| S002        | 李四          | CBAB             |      1 | C                                       |
| S002        | 李四          | CBAB             |      2 | B                                       |
| S002        | 李四          | CBAB             |      3 | A                                       |
| S002        | 李四          | CBAB             |      4 | B                                       |
| S003        | 王五          | BBAC             |      1 | B                                       |
| S003        | 王五          | BBAC             |      2 | B                                       |
| S003        | 王五          | BBAC             |      3 | A                                       |
| S003        | 王五          | BBAC             |      4 | C                                       |
| S004        | 赵六          | BACA             |      1 | B                                       |
| S004        | 赵六          | BACA             |      2 | A                                       |
| S004        | 赵六          | BACA             |      3 | C                                       |
| S004        | 赵六          | BACA             |      4 | A                                       |
+-------------+---------------+------------------+--------+-----------------------------------------+


上面已经很简便的得到结果了,其实mysql还有一个函数:substring函数,能实现right和left相同的功能;


##substring函数,结果应该和上面一样
select employee_id,employee_name,assessment_level,seq_num as 季度,substring(assessment_level,seq_num,1)
from employee_assessment,sequence where length(assessment_level) >= seq_num;


上面其实只是处理了assessment_level字段,sales_amount 字段还没有处理,值是以逗号分隔的 :

##mysql有这样一个函数:substring_index函数  
##substring_index有三个参数:第一个是:要分隔的字段,第二个是:以什么分隔,第三个是:要取第几个逗号前面的数据
##还嵌套了一层substring_index,-1:从右边往前取一个,以逗号分隔
##这里的where,是先计算出逗号的个数,然后加1,在去和sequence表中的数据比较

select employee_id,employee_name,assessment_level,seq_num,sales_amount,substring_index(substring_index(sales_amount,,,seq_num),,,-1)
from employee_assessment,sequence
WHERE LENGTH(sales_amount) - LENGTH(REPLACE(sales_amount,,,‘‘))+1 >= seq_num
ORDER BY employee_id;

SQL进阶-行转列&列转行

原文:https://www.cnblogs.com/weiyiming007/p/11458521.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!