zoukankan      html  css  js  c++  java
  • SQL进阶-行转列&列转行

    一、行转列

    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;

    结果:

    image


    二、列转行

    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;
  • 相关阅读:
    adobe acrobat 无效批注对象
    分享下今天研究的流量上限DDos攻击分析和解决方式
    【二】【HTML列表、表格与框架】
    大话计算机中的流水作业
    texinfo
    texindex
    texi2dvi
    tex, virtex, initex
    testprns printername [printcapname]
    testparm
  • 原文地址:https://www.cnblogs.com/weiyiming007/p/11458521.html
Copyright © 2011-2022 走看看