zoukankan      html  css  js  c++  java
  • SQL进阶系列之1CASE表达式

    C:PostgreSQL9.5inpsql.exe –U postgres -d test
    

    写在前面

    CASE表达式是从SQL-92标准引入的,强烈建议将MySQL的IF、Oracle的DECODE用CASE表达式替代

    CASE表达式概述

    • 简单表达式(simple case expression)和搜索表达式(searched case expression):简单表达式写法更简单,但实现功能有限。
    • CASE表达式注意事项:
      • 统一各分支返回的数据类型
      • 不要忘了写END
      • 养成写ELSE字句的习惯

    将已有编号方式转换为新的方式并统计

    /* 将已有编号方式转换为新的方式并统计 */
    CREATE TABLE PopTbl
    (pref_name VARCHAR(32) PRIMARY KEY,
    population INTEGER NOT NULL);
    
    INSERT INTO PopTbl VALUES('德岛', 100);
    INSERT INTO PopTbl VALUES('香川', 200);
    INSERT INTO PopTbl VALUES('爱媛', 150);
    INSERT INTO PopTbl VALUES('高知', 200);
    INSERT INTO PopTbl VALUES('福冈', 300);
    INSERT INTO PopTbl VALUES('佐贺', 100);
    INSERT INTO PopTbl VALUES('长崎', 200);
    INSERT INTO PopTbl VALUES('东京', 400);
    INSERT INTO PopTbl VALUES('群马', 50);
    
    /* 把县编号转换成地区编号(1) */
    SELECT CASE pref_name
                 WHEN '德岛' THEN '四国'
                 WHEN '香川' THEN '四国'
                 WHEN '爱媛' THEN '四国'
                 WHEN '高知' THEN '四国'
                 WHEN '福冈' THEN '九州'
                 WHEN '佐贺' THEN '九州'
                 WHEN '长崎' THEN '九州'
                 ELSE '其他' END AS district,
           SUM(population)
     FROM PopTbl
     GROUP BY CASE pref_name
                 WHEN '德岛' THEN '四国'
                 WHEN '香川' THEN '四国'
                 WHEN '爱媛' THEN '四国'
                 WHEN '高知' THEN '四国'
                 WHEN '福冈' THEN '九州'
                 WHEN '佐贺' THEN '九州'
                 WHEN '长崎' THEN '九州'
                 ELSE '其他' END;
    
    /* 按人口数量等级划分都道府县 */
    SELECT CASE WHEN population < 100 THEN '01'
                WHEN population >= 100 AND population < 200 THEN '02'
                WHEN population >= 200 AND population < 300 THEN '03'
                WHEN population >= 300 THEN '04'
                ELSE NULL END AS pop_class,
           COUNT(*) AS cnt
      FROM PopTbl
     GROUP BY CASE WHEN population < 100 THEN '01'
                   WHEN population >= 100 AND population < 200 THEN '02'
                   WHEN population >= 200 AND population < 300 THEN '03'
                   WHEN population >= 300 THEN '04'
                   ELSE NULL END;
    
    /* 把县编号转换成地区编号(2):将CASE表达式归纳到一处 */
    SELECT CASE pref_name
                  WHEN '德岛' THEN '四国'
                  WHEN '香川' THEN '四国'
                  WHEN '爱媛' THEN '四国'
                  WHEN '高知' THEN '四国'
                  WHEN '福冈' THEN '九州'
                  WHEN '佐贺' THEN '九州'
                  WHEN '长崎' THEN '九州'
                  ELSE '其他' END AS district,
           SUM(population)
      FROM PopTbl
     GROUP BY district; -- 正常情况下,GROUP BY字句在SELECT之前运行,但此处不报错。
    

    用一条SQL语句进行不同条件的统计

    /* 用一条SQL语句进行不同条件的统计 */
    CREATE TABLE PopTbl2
    (pref_name VARCHAR(32),
     sex CHAR(1) NOT NULL,
     population INTEGER NOT NULL,
        PRIMARY KEY(pref_name, sex));
    
    INSERT INTO PopTbl2 VALUES('德岛', '1',	60 );
    INSERT INTO PopTbl2 VALUES('德岛', '2',	40 );
    INSERT INTO PopTbl2 VALUES('香川', '1',	100);
    INSERT INTO PopTbl2 VALUES('香川', '2',	100);
    INSERT INTO PopTbl2 VALUES('爱媛', '1',	100);
    INSERT INTO PopTbl2 VALUES('爱媛', '2',	50 );
    INSERT INTO PopTbl2 VALUES('高知', '1',	100);
    INSERT INTO PopTbl2 VALUES('高知', '2',	100);
    INSERT INTO PopTbl2 VALUES('福冈', '1',	100);
    INSERT INTO PopTbl2 VALUES('福冈', '2',	200);
    INSERT INTO PopTbl2 VALUES('佐贺', '1',	20 );
    INSERT INTO PopTbl2 VALUES('佐贺', '2',	80 );
    INSERT INTO PopTbl2 VALUES('长崎', '1',	125);
    INSERT INTO PopTbl2 VALUES('长崎', '2',	125);
    INSERT INTO PopTbl2 VALUES('东京', '1',	250);
    INSERT INTO PopTbl2 VALUES('东京', '2',	150);
    
    /* 用一条SQL语句进行不同条件的统计 */
    SELECT pref_name,
           /* 男性人口 */
           SUM( CASE WHEN sex = '1' THEN population ELSE 0 END) AS cnt_m,
           /* 女性人口 */
           SUM( CASE WHEN sex = '2' THEN population ELSE 0 END) AS cnt_f
      FROM PopTbl2
     GROUP BY pref_name;
    

    新手用WHERE字句进行条件分支,高手用SELECT字句进行条件分支

    用CHECK约束定义多个列的条件关系

    /* 用CHECK约束定义多个列的条件关系 */
    CREATE TABLE TestSal
    (sex CHAR(1) ,
     salary INTEGER,
        CONSTRAINT check_salary CHECK
                 ( CASE WHEN sex = '2'
                        THEN CASE WHEN salary <= 200000
                                  THEN 1 ELSE 0 END
                        ELSE 1 END = 1 ));
    
    INSERT INTO TestSal VALUES(1, 200000);
    INSERT INTO TestSal VALUES(1, 300000);
    INSERT INTO TestSal VALUES(1, NULL);
    INSERT INTO TestSal VALUES(2, 200000);
    INSERT INTO TestSal VALUES(2, 300000);  --error
    INSERT INTO TestSal VALUES(2, NULL);
    INSERT INTO TestSal VALUES(1, 300000);
    
    /* 用CHECK约束定义多个列的条件关系 */
    /* 蕴含式 */
    CONSTRAINT check_salary CHECK
    ( CASE WHEN sex = '2'
           THEN CASE WHEN salary <= 200000
                     THEN 1 ELSE 0 END
           ELSE 1 END = 1 )
    

    SQL三值逻辑的逻辑与和蕴含式

    P Q P ^ Q P Q P -> Q
    T T T T T T
    T F T T F F
    T U U T U T
    F T F F T T
    F F F F F T
    F U F F U T
    U T U U T T
    U F F U F T
    U U U U U T

    在UPDATE语句里进行条件分支

    -- 对当前工资为30万日元以上的员工,降薪20%
    -- 同时对当前工资为25万日元以上且不满28万日元的员工,加薪20%
    /* 用CASE表达式写正确的更新操作 */
    UPDATE Salaries
       SET salary = CASE WHEN salary >= 300000
                         THEN salary * 0.9
                         WHEN salary >= 250000 AND salary < 280000
                         THEN salary * 1.2
                         ELSE salary END;
    -- 如果不写ELSE则不满足CASE WHEN语句的将会被处理成NULL
    

    表之间的数据匹配

    /* 表之间的数据匹配 */
    CREATE TABLE CourseMaster
    (course_id   INTEGER PRIMARY KEY,
     course_name VARCHAR(32) NOT NULL);
    
    INSERT INTO CourseMaster VALUES(1, '会计入门');
    INSERT INTO CourseMaster VALUES(2, '财务知识');
    INSERT INTO CourseMaster VALUES(3, '簿记考试');
    INSERT INTO CourseMaster VALUES(4, '税务师');
    
    CREATE TABLE OpenCourses
    (month       INTEGER ,
     course_id   INTEGER ,
        PRIMARY KEY(month, course_id));
    
    INSERT INTO OpenCourses VALUES(200706, 1);
    INSERT INTO OpenCourses VALUES(200706, 3);
    INSERT INTO OpenCourses VALUES(200706, 4);
    INSERT INTO OpenCourses VALUES(200707, 4);
    INSERT INTO OpenCourses VALUES(200708, 2);
    INSERT INTO OpenCourses VALUES(200708, 4);
    
    /* 表的匹配:使用IN谓词 */
    SELECT CM.course_name,
           CASE WHEN CM.course_id IN 
                        (SELECT course_id FROM OpenCourses 
                          WHERE month = 200706) THEN '○'
                ELSE '×' END AS "6月",
           CASE WHEN CM.course_id IN 
                        (SELECT course_id FROM OpenCourses
                          WHERE month = 200707) THEN '○'
                ELSE '×' END AS "7月",
           CASE WHEN CM.course_id IN 
                        (SELECT course_id FROM OpenCourses
                          WHERE month = 200708) THEN '○'
                ELSE '×' END  AS "8月"
      FROM CourseMaster CM;
    
    /* 表的匹配:使用EXISTS谓词 */
    SELECT CM.course_name,
           CASE WHEN EXISTS
                        (SELECT course_id FROM OpenCourses OC
                          WHERE month = 200706
                            AND CM.course_id = OC.course_id) THEN '○'
                ELSE '×' END AS "6月",
           CASE WHEN EXISTS
                        (SELECT course_id FROM OpenCourses OC
                          WHERE month = 200707
                            AND CM.course_id = OC.course_id) THEN '○'
                ELSE '×' END AS "7月",
           CASE WHEN EXISTS
                        (SELECT course_id FROM OpenCourses OC
                          WHERE month = 200708
                            AND CM.course_id = OC.course_id) THEN '○'
                ELSE '×' END  AS "8月"
      FROM CourseMaster CM;
    

    在CASE表达式中使用聚合函数

    /* 在CASE表达式中使用聚合函数 */
    CREATE TABLE StudentClub
    (std_id  INTEGER,
     club_id INTEGER,
     club_name VARCHAR(32),
     main_club_flg CHAR(1),
     PRIMARY KEY (std_id, club_id));
    
    INSERT INTO StudentClub VALUES(100, 1, '棒球',        'Y');
    INSERT INTO StudentClub VALUES(100, 2, '管弦乐',      'N');
    INSERT INTO StudentClub VALUES(200, 2, '管弦乐',      'N');
    INSERT INTO StudentClub VALUES(200, 3, '羽毛球','Y');
    INSERT INTO StudentClub VALUES(200, 4, '足球',    'N');
    INSERT INTO StudentClub VALUES(300, 4, '足球',    'N');
    INSERT INTO StudentClub VALUES(400, 5, '游泳',        'N');
    INSERT INTO StudentClub VALUES(500, 6, '围棋',        'N');
    
    /* 在CASE表达式中使用聚合函数 */
    SELECT std_id,
           CASE WHEN COUNT(*) = 1 /* 只加入了一个社团的学生 */
                THEN MAX(club_id)
                ELSE MAX(CASE WHEN main_club_flg = 'Y'
                              THEN club_id
                              ELSE NULL END)
            END AS main_club
      FROM StudentClub
     GROUP BY std_id;
    

    新手用HAVING子句进行条件分支,高手用SELECT子句进行分支

    小结

    • 在GROUP BY子句里使用CASE表达式,可以灵活地选择作为聚合的单位的编号和等级。适用非定制化分组统计
    • 在聚合函数中使用CASE表达式,可以轻松地将行结构转换为列结构的数据
    • 相反,聚合函数也可以嵌套进CASE表达式里使用
    • 相比依赖于具体数据库的函数,CASE表达式有更强大的表达能力和更好的移植性

    练习题

    /*多列数据的最大值*/
    /* 练习题1-1:多列数据的最大值(练习题1-1-3也会用到) */
    CREATE TABLE Greatests
    (key CHAR(1) PRIMARY KEY,
     x   INTEGER NOT NULL,
     y   INTEGER NOT NULL,
     z   INTEGER NOT NULL);
    
    INSERT INTO Greatests VALUES('A', 1, 2, 3);
    INSERT INTO Greatests VALUES('B', 5, 5, 2);
    INSERT INTO Greatests VALUES('C', 4, 7, 1);
    INSERT INTO Greatests VALUES('D', 3, 3, 8);
    
    /* 求x、y和z中的最大值 */
    SELECT key,
           CASE WHEN CASE WHEN x < y THEN y ELSE x END < z
                THEN z
                ELSE CASE WHEN x < y THEN y ELSE x END
            END AS greatest
      FROM Greatests;
    
    
    /*转换行列--在表头里加入汇总和再揭*/
    SELECT CASE WHEN sex = '1' THEN '男' ELSE '女' END AS 性别,
    	   SUM(population) AS 全国,
    	   SUM(CASE WHEN pref_name = '德岛' THEN population ELSE 0 END) AS 德岛,
    	   SUM(CASE WHEN pref_name = '香川' THEN population ELSE 0 END) AS 香川,
    	   SUM(CASE WHEN pref_name = '爱媛' THEN population ELSE 0 END) AS 爱媛,
    	   SUM(CASE WHEN pref_name = '高知' THEN population ELSE 0 END) AS 高知,
    	   SUM(CASE WHEN pref_name IN ('德岛','香川','爱媛','高知') THEN population ELSE 0 END) AS 四国再揭
    FROM PopTbl2
    GROUP BY 性别
    ORDER BY 性别 ASC;
    
    /*用ORDER BY生成排序列*/
    SELECT *
    FROM Greatests
    ORDER BY CASE key
    		 WHEN 'B' THEN 1 
    		 WHEN 'A' THEN 2
    		 WHEN 'D' THEN 3
    		 WHEN 'C' THEN 4
    		 ELSE NULL END;
    
  • 相关阅读:
    Elasticsearch倒排索引结构
    Redis(二)冰叔带你了解Redis-哨兵模式和高可用集群解析
    jenkins构建配置
    jenkins配置email
    jenkins配置maven
    jenkins配置java
    jenkins配置git
    centos安装jenkins
    centos安装tomcat
    centos安装jdk
  • 原文地址:https://www.cnblogs.com/evian-jeff/p/11436707.html
Copyright © 2011-2022 走看看