SQL学习—用SQL处理数列

1-9 用SQL处理数列

生成连续编号

通过对两个Digits集合求笛卡尔儿积而得出0~99的数字。

-- 求连续编号(1):求0~99 的数
SELECT 
    D1.digit + (D2.digit * 10) AS seq
FROM
    Digits D1
        CROSS JOIN
    Digits D2
ORDER BY seq

求全部的缺失编号

/* 动态地指定连续编号范围的SQL语句 */
SELECT seq
  FROM Sequence
 WHERE seq BETWEEN (SELECT MIN(seq) FROM SeqTbl)
               AND (SELECT MAX(seq) FROM SeqTbl)
EXCEPT
SELECT seq FROM SeqTbl;

三个人能坐得下吗

应用场景:火车座位查找连座的组合。从1~15座位编号中,找出连续3个空位的全部组合。
我们把由连续的整数构成的集合,也就是连续编号的集合称为“序列”。
满足条件:以n为起点,n+(3-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 <> '未预订' )
ORDER BY start_seat;

其中:":head_cnt"表示需要的空位个数的参数。
解释:
(1)通过自连接生成起点和终点的组合
S2.seat = S1.seat + (:head_cnt -1) 排除了像1~8、
2~3这样长度不是3的组合,从而保证结果中出现只有从起点和终点刚好包含3个空位的序列。
(2)描述起点和终点之间所有的点需要满足的条件
限定移动范围时使用BETWEEN谓词很方便
在本例中,序列内的点需要满足的条件“所有座位的状态都是‘未预订’”。
全称量化的命题。一般思路都是把“所有行都满足条件P”转换成双重否定——“不存在不满足条件P的行”。因此子查询的条件为“ S3.status <> ‘未预订’ ”。
升级版
存在换排的情况。假设火车每一排有5个座位,我们在表中加上行编号“row_id”列。
即考虑座位的折返

/* 找出需要的空位(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))
ORDER BY start_seat;

序列内的点需要满足的条件是,“所有座位的状态都是’未预订‘,且行编号相同”。这里新加的“行编号相同”等价于“与起点的行编号相同”。把这个条件直接写成SQL语句的话,像下面这样。

S3.status = '未预订' AND S3.row_id = S1.row_id

由于SQL中不存在全称量词,所以我们必须使用这个条件的否定。

最多能坐下多少人

与上一道题相反,这次查询的是“按现在的空位状况,最多能坐下多少人”。换句话说,要求的是最长序列。

  • 条件1:起点到终点之间所有座位状态都是“未预订”
  • 条件2:起点之前的座位状态不是“未预订”
  • 条件3:终点之后的座位状态不是“未预订”

思路
(1)生成一张存储了所有序列的视图

/* 第一阶段:生成存储了所有序列的视图 */
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的否定 */

(2)从视图中找到座位数(seat_cnt)最大的一行数据

/* 第二阶段:求最长的序列 */
SELECT start_seat, '~', end_seat, seat_cnt
  FROM Sequences
 WHERE seat_cnt = (SELECT MAX(seat_cnt) FROM Sequences);

总结:这道例题也一样,首先第一步,通过自连接"S1.seat<=S2.seat",求出起点和终点的组合。
之后为了描述起点终点之间移动的点的集合S3,然后使用存在量化的否定形式来表达全称量化。

单调递增和单调递减

应用场景:假设存在一张反映某公司股价动态的表。查找股价单调递增的时间区间。
思路
(1)自连接生成起点和终点的组合。

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

(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/* 第一步:生成起点和终点的组合 */
        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)

因为我们需要取区间内的两个点,所以需要相应地增加两个集合,即S3和S4。子查询里的两个BETWEEN谓词保证S3和S4的移动范围在区间内(处理有序集合的时BETWEEN谓词真的很好用)。后面的 S3.deal_date < S4.deal_date 描述了 S4 里的日期比 S3 里的日期晚的条件。最后的 S3.price >= S4.price 描述了过去的股价更高(或者持平)的条件。
另,这个查询结果包含了子集。最后我们要把这些不需要的子集排除掉,需要使用极值函数。

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

本节小结

本节主要介绍了以数列为代表的有序集合的处理方法。本节要点:
01 SQL处理数据的方法有两种

  • 第一种 把数据看作忽略顺序的集合
  • 第二种 把数据看作有序的集合,基本方法:
    a 首先用自连接生成起点和终点的组合
    b 其次在子查询中描述内部的各个元素之间必须满足的关系

02 要在SQL中表达全称量化时,需要将全称量化命题转换成存在量化命题的否定形式,并使用NOT EXISTS 谓词。这是因为SQL只实现了谓词逻辑中的存在量词。

猜你喜欢

转载自blog.csdn.net/weixin_43387060/article/details/86546369