读SQL进阶教程笔记13_SQL中的分组和层级

Posted 躺柒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了读SQL进阶教程笔记13_SQL中的分组和层级相关的知识,希望对你有一定的参考价值。

1. 数据分组

1.1. SQL的语句中具有分组功能的是GROUP BY和PARTITION BY

1.1.1. 两者都有数学的理论基础

1.1.2. 都可以根据指定的列为表分组

1.1.3. 区别仅仅在于,GROUP BY在分组之后会把每个分组聚合成一行数据

1.1.4. GROUP BY的作用是将一个个元素划分成若干个子集

1.2. 示例

1.2.1.

 SELECT member, team, age ,

          RANK() OVER(PARTITION BY team ORDER BY age DESC) rn,
          DENSE_RANK() OVER(PARTITION BY team ORDER BY age DESC) dense_rn,
          ROW_NUMBER() OVER(PARTITION BY team ORDER BY age DESC) row_num
      FROM Members
     ORDER BY team, rn;

1.3. 分割后的子集

1.3.1. 它们全都是非空集合

1.3.1.1. 还有一种只包含NULL的集合

1.3.2. 所有子集的并集等于划分之前的集合

1.3.3. 任何两个子集之间都没有交集

1.3.4. 满足以上3个性质的各子集称为“类”(partition)

1.4. 类的概念(即partition)

1.4.1. 群论中有很多非常有趣的类,比如“剩余类”

1.4.1.1. 通过对3取余给自然数集合N分类后

1.4.1.1.1. “模3剩余类”

1.4.1.2. 模在SQL中也有实现,就是取模函数MOD

1.4.1.2.1. --对从1到10的整数以3为模求剩余类
    SELECT MOD(num, 3) AS modulo,
          num
      FROM Natural
     ORDER BY modulo, num;

1.4.1.3. --从原来的表中抽出(大约)五分之一行的数据

    SELECT *
      FROM SomeTbl
     WHERE MOD(seq, 5) = 0;
    --表中没有连续编号的列时,使用ROW_NUMBER函数就可以了
    SELECT *
      FROM (SELECT col,
                  ROW_NUMBER() OVER(ORDER BY col) AS seq
              FROM SomeTbl)
     WHERE MOD(seq, 5) = 0;

1.4.2. 正因为抽象,才有了广泛的应用

1.4.2.1. 数学理论并不是脱离实际的游戏,它其实隐藏了大量能够用于日常工作的技巧

2. 层级

2.1. SQL的世界其实是层级分明的等级社会

2.2. 使用GROUP BY聚合之后,我们就不能引用原表中除聚合键之外的列

2.2.1. 这只是SQL中的一种逻辑,是为了严格区分层级

2.3. GROUP BY中的阶与元素和集合的区别有关,因此属于集合论中的阶

2.3.1. 对于EXISTS来说,层级的差别与EXISTS谓词及其参数有关,因此属于谓词逻辑中的阶

2.3.2. 使用GROUP BY聚合之后,SQL的操作对象便由0阶的“行”变为了1阶的“行的集合”

2.4. 标准SQL规定

2.4.1. 在对表进行聚合查询的时候,只能在SELECT子句中写下面3种内容

2.4.2. 通过GROUP BY子句指定的聚合键

2.4.3. 聚合函数(SUM、AVG等)

2.4.4. 常量

2.5. 示例

2.5.1. --以组为单位进行聚合查询

    SELECT team, AVG(age)
      FROM Teams
     GROUP BY team;

2.5.1.1. 年龄只是每个人的属性,而不是小组的属性

2.5.1.2. 小组指的是由多个人组成的集合

2.5.1.2.1. 小组的属性只能是平均或者总和等统计性质的属性

2.5.2. --以组为单位进行聚合查询?

    SELECT team, AVG(age), age
      FROM Teams
     GROUP BY team;

2.5.2.1. 违反了标准SQL的规定,因此不具有可移植性

2.5.2.2. MySQL数据库支持这样的查询语句

2.5.2.3. 强行将适用于个体的属性套用于团体之上,纯粹是一种分类错误

2.5.3. --错误

    SELECT team, AVG(age), member
      FROM Teams
     GROUP BY team;

2.5.3.1. --正确

    SELECT team, AVG(age), MAX(member)
      FROM Teams
     GROUP BY team;

2.5.4. --小组中年龄最大的成员

SELECT team, MAX(age),
          (SELECT MAX(member)
              FROM Teams T2
            WHERE T2.team = T1.team
              AND T2.age = MAX(T1.age)) AS oldest
      FROM Teams T1
     GROUP BY team;

2.5.4.1. 子查询中的WHERE子句里使用了MAX(T1.age)这样的聚合函数作为条件

2.5.4.2. 这里对外层的表T1也进行了聚合,这样一来我们就可以在SELECT子句中通过聚合函数来引用“age”列了

2.5.4.3. 不能反过来在子查询中直接引用“age”列

2.6. 单元素集合也是集合

2.6.1. 单元素集合和空集一样,主要是为了保持理论的完整性而定义的

2.6.2. 元素a和集合a之间存在着非常醒目的层级差别

2.6.2.1. 这两个层级的区别分别对应着SQL中的WHERE子句和HAVING子句的区别

2.6.2.2. WHERE子句用于处理“行”这种0阶的对象

2.6.2.3. HAVING子句用来处理“集合”这种1阶的对象

读SQL进阶教程笔记08_处理数列

1. 处理有序集合也并非SQL的直接用途

1.1. SQL语言在处理数据时默认地都不考虑顺序

2. 处理数据的方法有两种

2.1. 第一种是把数据看成忽略了顺序的集合

2.2. 第二种是把数据看成有序的集合

2.2.1. 首先用自连接生成起点和终点的组合

2.2.2. 其次在子查询中描述内部的各个元素之间必须满足的关系

2.2.2.1. 要在SQL中表达全称量化时,需要将全称量化命题转换成存在量化命题的否定形式,并使用NOT EXISTS谓词

3. 生成连续编号

3.1. 序列对象(sequence object)

3.1.1. CONNECT BY(Oracle)

3.1.2. WITH子句(DB2、SQL Server)

3.1.3. 依赖数据库实现的方法

3.2. 示例

3.2.1.

3.2.1.1. --求连续编号(1):求0~99的数

    SELECT D1.digit + (D2.digit * 10)  AS seq
      FROM Digits D1 CROSS JOIN Digits D2
     ORDER BY seq;

3.2.1.2. --求连续编号(2):求1~542的数

    SELECT D1.digit + (D2.digit * 10) + (D3.digit * 100) AS seq
      FROM Digits D1 CROSS JOIN Digits D2
            CROSS JOIN Digits D3
     WHERE D1.digit + (D2.digit * 10)
                    + (D3.digit * 100) BETWEEN 1 AND 542
     ORDER BY seq;

3.2.1.3. --生成序列视图(包含0~999)

    CREATE VIEW Sequence (seq)
    AS SELECT D1.digit + (D2.digit * 10) + (D3.digit * 100)
        FROM Digits D1 CROSS JOIN Digits D2
                CROSS JOIN Digits D3;
3.2.1.3.1. --从序列视图中获取1~100
    SELECT seq
      FROM Sequence
     WHERE seq BETWEEN 1 AND 100
     ORDER BY seq;

3.3. 冯·诺依曼的方法使用递归集合定义自然数,先定义0然后得到1,定义1然后得到2,是有先后顺序的

3.3.1. 适用于解决位次、累计值等与顺序相关的问题

3.4. 这里的解法完全丢掉了顺序这一概念,仅把数看成是数字的组合。这种解法更能体现出SQL语言的特色

4. 求全部的缺失编号

4.1. 示例

4.1.1. --EXCEPT版

    SELECT seq
      FROM Sequence
     WHERE seq BETWEEN 1 AND 12
    EXCEPT
    SELECT seq FROM SeqTbl;

4.1.1.1. --NOT IN版

    SELECT seq
      FROM Sequence
     WHERE seq BETWEEN 1 AND 12
      AND seq NOT IN (SELECT seq FROM SeqTbl);

4.1.2. --动态地指定连续编号范围的SQL语句

    SELECT seq
      FROM Sequence
     WHERE seq BETWEEN (SELECT MIN(seq) FROM SeqTbl)
                  AND (SELECT MAX(seq) FROM SeqTbl)
    EXCEPT
    SELECT seq FROM SeqTbl;

4.1.2.1. 查询上限和下限未必固定的表时非常方便

4.1.2.2. 两个自查询没有相关性,而且只会执行一次

4.1.2.3. 如果在“seq”列上建立索引,那么极值函数的运行可以变得更快速

5. 座位预订

5.1. 三个人能坐得下吗

5.1.1.

5.1.1.1. --找出需要的空位(1):不考虑座位的换排

    SELECT S1.seat   AS start_seat, \'~\', S2.seat AS end_seat
      FROM Seats S1, Seats S2
     WHERE S2.seat = S1.seat + (:head_cnt -1)  --决定起点和终点
      AND NOT EXISTS
              (SELECT *
                FROM Seats S3
                WHERE S3.seat BETWEEN S1.seat AND S2.seat
                  AND S3.status <>’未预订’);
5.1.1.1.1. “:head_cnt”是表示需要的空位个数的参数
5.1.1.1.2. 如果不减1,会多取一个座位

5.1.1.2. 第一步:通过自连接生成起点和终点的组合

5.1.1.2.1. S2.seat = S1.seat + (:head_cnt-1)的部分
5.1.1.2.2. 排除掉了像1~8、2~3这样长度不是3的组合

5.1.1.3. 第二步:描述起点到终点之间所有的点需要满足的条件

5.1.1.3.1. 序列内的点需要满足的条件“所有座位的状态都是‘未预订’”

5.1.1.4. --找出需要的空位(2):考虑座位的换排

    SELECT S1.seat   AS start_seat, \'~\', S2.seat AS end_seat
      FROM Seats2 S1, Seats2 S2
     WHERE S2.seat = S1.seat + (:head_cnt -1)  --决定起点和终点
      AND NOT EXISTS
              (SELECT *
                FROM Seats2 S3
                WHERE S3.seat BETWEEN S1.seat AND S2.seat
                  AND (    S3.status <>’未预订’
                        OR S3.row_id <> S1.row_id));
5.1.1.4.1. 所有座位的状态都是‘未预订’,且行编号相同

5.2. 最多能坐下多少人

5.2.1.

5.2.1.1. 条件1:起点到终点之间的所有座位状态都是“未预订”

5.2.1.2. 条件2:起点之前的座位状态不是“未预订”

5.2.1.3. 条件3:终点之后的座位状态不是“未预订”

5.2.2. --第一阶段:生成存储了所有序列的视图

    CREATE VIEW Sequences (start_seat, end_seat, seat_cnt) AS
    SELECT S1.seat  AS start_seat,
          S2.seat  AS end_seat,
          S2.seat - S1.seat + 1 AS seat_cnt
      FROM Seats3 S1, Seats3 S2
     WHERE S1.seat <= S2.seat  --第一步:生成起点和终点的组合
        AND NOT EXISTS    --第二步:描述序列内所有点需要满足的条件
            (SELECT *
              FROM Seats3 S3
              WHERE (     S3.seat BETWEEN S1.seat AND S2.seat
                      AND S3.status <>’未预订’)  --条件1的否定
                OR  (S3.seat = S2.seat + 1 AND S3.status =’未预订’)
                                                        --条件2的否定
                OR  (S3.seat = S1.seat -1 AND S3.status =’未预订’));
                                                        --条件3的否定

5.2.2.1. --第二阶段:求最长的序列

    SELECT start_seat, \'~\', end_seat, seat_cnt
      FROM Sequences
     WHERE seat_cnt = (SELECT MAX(seat_cnt) FROM Sequences);

6. 单调递增和单调递减

6.1. 示例

6.1.1.

6.1.2. --生成起点和终点的组合的SQL语句

    SELECT S1.deal_date  AS start_date,
          S2.deal_date  AS end_date
      FROM MyStock S1, MyStock S2
     WHERE S1.deal_date < S2.deal_date;

6.1.2.1. --求单调递增的区间的SQL语句:子集也输出

    SELECT S1.deal_date   AS start_date,
          S2.deal_date   AS end_date
      FROM MyStock S1, MyStock S2
     WHERE S1.deal_date < S2.deal_date  --第一步:生成起点和终点的组合
      AND  NOT EXISTS
              ( SELECT *  --第二步:描述区间内所有日期需要满足的条件
                  FROM MyStock S3, MyStock S4
                  WHERE S3.deal_date BETWEEN S1.deal_date AND S2.deal_date
                  AND S4.deal_date BETWEEN S1.deal_date AND S2.deal_date
                    AND S3.deal_date < S4.deal_date
                    AND S3.price >= S4.price);
6.1.2.1.1. --排除掉子集,只取最长的时间区间
    SELECT MIN(start_date) AS start_date,      --最大限度地向前延伸起点
          end_date
      FROM  (SELECT S1.deal_date AS start_date,
                    MAX(S2.deal_date) AS end_date  --最大限度地向后延伸终点
              FROM MyStock S1, MyStock S2
              WHERE S1.deal_date < S2.deal_date
                AND NOT EXISTS
                (SELECT *
                    FROM MyStock S3, MyStock S4
                  WHERE S3.deal_date BETWEEN S1.deal_date AND S2.deal_date
                    AND S4.deal_date BETWEEN S1.deal_date AND S2.deal_date
                    AND S3.deal_date < S4.deal_date
                    AND S3.price >= S4.price)
            GROUP BY S1.deal_date) TMP
    GROUP BY end_date;

以上是关于读SQL进阶教程笔记13_SQL中的分组和层级的主要内容,如果未能解决你的问题,请参考以下文章

读SQL进阶教程笔记08_处理数列

读SQL进阶教程笔记05_关联子查询

读SQL进阶教程笔记14_SQL编程要点

读SQL进阶教程笔记06_外连接

读SQL进阶教程笔记02_三值逻辑和NULL

读SQL进阶教程笔记16_SQL优化让SQL飞起来