本文是《sql进阶教程》阅读笔记,感兴趣可以阅读该书对应章节,这本适合有一定sql基础的同学阅读。另外作者《sql基础教程》也值得一看
生成连续编号
在思考这道例题之前,请先思考下面一个问题:
00 ~ 99 的 100 个数中,0, 1, 2,…, 9 这 10 个数字分别出现了多少次?
如果把数看成字符串,其实它就是由各个数位上的数字组成的集合
Digits
| digit( 数字 ) |
|---|
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
通过对两个 Digits 集合求笛卡儿积而得出 0 ~ 99的数字。
create table Digits (
digit INTEGER
);
INSERT INTO Digits(digit)VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
-- 求连续编号(1):求0~99 的数
SELECT D1.digit + (D2.digit * 10) AS seq
FROM Digits D1 CROSS JOIN Digits D2
ORDER BY seq;
通过追加 D3、D4 等集合,不论多少位的数都可以生成。而且,如果只想生成从 1 开始,或者到 542 结束的数,只需在 WHERE子句中加入过滤条件就可以了。
-- 求连续编号(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;
通过将这个查询的结果存储在视图里,就可以在需要连续编号时通过简单的 SELECT 来获取需要的编号
-- 生成序列视图(包含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;
-- 从序列视图中获取1~100
SELECT seq
FROM Sequence
WHERE seq BETWEEN 1 AND 100
ORDER BY seq;
求全部的缺失编号
查找连续编号中的缺失编号的方法。作为示例,假设存在下面这样一张编号有缺失的表。
Seqtbl
| seq(连续编号) |
|---|
| 1 |
| 2 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 10 |
| 11 |
| 12 |
缺失的编号 3、9、10
--EXCEPT 版
SELECT seq
FROM Sequence
WHERE seq BETWEEN 1 AND 12
EXCEPT
SELECT seq FROM SeqTbl;
--NOT IN 版
SELECT seq
FROM Sequence
WHERE seq BETWEEN 1 AND 12
AND seq NOT IN (SELECT seq FROM SeqTbl);
-- 动态地指定连续编号范围的SQL 语句
--像下面这么做性能会有所下降,但是通过扩
--展 BETWEEN 谓词的参数,可以动态地指定目标表的最大值和最
SELECT seq
FROM Sequence
WHERE seq BETWEEN (SELECT MIN(seq) FROM SeqTbl)
AND (SELECT MAX(seq) FROM SeqTbl)
EXCEPT
SELECT seq FROM SeqTbl;
这种写法在查询上限和下限未必固定的表时非常方便。两个自查询没有相关性,
而且只会执行一次。如果在“seq”列上建立索引,那么极值函数的运行可以变得更快速。
三个人能坐得下吗
预约火车票或机票时考虑连坐问题
Seats
| seat ( 座位 ) | status ( 状态 ) |
|---|---|
| 1 | 已预订 |
| 2 | 已预订 |
| 3 | 未预订 |
| 4 | 未预订 |
| 5 | 未预订 |
| 6 | 已预订 |
| 7 | 未预订 |
| 8 | 未预订 |
| 9 | 未预订 |
| 10 | 未预订 |
| 11 | 未预订 |
| 12 | 已预订 |
| 13 | 已预订 |
| 14 | 未预订 |
| 15 | 未预订 |
要求:找出连续 3 个空位的全部组合
把由连续的整数构成的集合,也就是连续编号的集合称为“序列”。这样序列中就不能出现缺失编号。
- 3~5
- 7 ~9
- 8 ~ 10
- 9 ~ 11
(7, 8, 9, 10, 11) 这个序列中,包含 3 个子序列 (7, 8, 9)、(8, 9, 10)、(9,10, 11),我们也把它们当成不同的序列。还有,通常火车的一排只有几个座位,所以可能我们表里的座位会分布在几排里,但我们暂时忽略掉这个问题,假设所有的座位排成了一条直线
借助上面的图表我们可以知道,需要满足的条件是,以 n 为起点、 n+(3-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 <> '未预订'
);
注:“:head_cnt ”是表示需要的空位个数的参数
接下来看一下这道例题的升级版,即发生换排的情况。假设这列火车每一排有 5 个座位。我们在表中加上表示行编号
row_id列。
Seats2
| seat ( 座位 ) | row_id( 行编号 ID) | status ( 状态 ) |
|---|---|---|
| 1 | A | 已预订 |
| 2 | A | 已预订 |
| 3 | A | 未预订 |
| 4 | A | 未预订 |
| 5 | A | 未预订 |
| 6 | B | 已预订 |
| 7 | B | 未预订 |
| 8 | B | 未预订 |
| 9 | B | 未预订 |
| 10 | B | 未预订 |
| 11 | C | 未预订 |
| 12 | C | 已预订 |
| 13 | C | 已预订 |
| 14 | C | 未预订 |
| 15 | C | 未预订 |
-- 找出需要的空位(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));
最多能坐下多少人
按现在的空位状况,最多能坐下多少人”。换句话说,要求的是最长的序列
Seats3
| seat ( 座位 ) | status ( 状态 ) |
|---|---|
| 1 | 已预订 |
| 2 | 未预订 |
| 3 | 未预订 |
| 4 | 未预订 |
| 5 | 未预订 |
| 6 | 已预订 |
| 7 | 未预订 |
| 8 | 已预订 |
| 9 | 未预订 |
| 10 | 未预订 |
长度为 4 的序列“2 ~ 5”就是我们的答案
条件 1:起点到终点之间的所有座位状态都是“未预订”。
条件 2:起点之前的座位状态不是“未预订”。
条件 3:终点之后的座位状态不是“未预订”
-- 第一阶段:生成存储了所有序列的视图
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 的否定
单调递增和单调递减
假设存在下面这样一张反映了某公司股价动态的表
MyStock
| deal_date( 交易日期 ) | price( 股价 ) |
|---|---|
| 2007-01-06 | 1000 |
| 2007-01-08 | 1050 |
| 2007-01-09 | 1050 |
| 2007-01-12 | 900 |
| 2007-01-13 | 880 |
| 2007-01-14 | 870 |
| 2007-01-16 | 920 |
| 2007-01-17 | 1000 |
-- 生成起点和终点的组合的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;
-- 求单调递增的区间的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
);
-- 排除掉子集,只取最长的时间区间
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技巧精粹
本文深入探讨SQL高级应用,包括连续编号生成、缺失编号查找、序列分析及座位预订问题的解决策略,通过实例解析复杂查询技巧。
1546

被折叠的 条评论
为什么被折叠?



