MySQL处理序列

“东风又作无情计,艳粉娇红吹满地”

最近工作上不忙,于是自己在学习。目前在看《SQL进阶教程》一书。该书我已上传到百度云,仅供大家学习使用。网盘连接请看:SQL进阶教程PDF下载
接下来从几个例子说明SQL语句如何处理序列:

订票三连坐(座位不分排)

和朋友们一起去旅行,预约火车票或机票时,却发现没有能让所有人挨着坐的空位,于是某个人不得不和大家分开坐——这样不爽的事情可能不少人都遭遇过吧。
我们假设存在下面这样一张存储了火车座位预订情况的表。
在这里插入图片描述
先假设这趟列车不分排,即所有座位均在一排。我们一行3个人旅行,订这趟列车的票。作图如下:
在这里插入图片描述
于是,我们希望得到的结果是下面四种:

●  3 ~ 5
●  7 ~ 9
●  8 ~ 10
●  9 ~ 11
SQL语句:

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 ”是表示需要的空位个数的参数。通过往这个参数里赋具体值,可以应对任意多个人的预约。这里我们需要的是3个人连坐,即(:head_cnt -1) = (3 -1) = 2(下同)。
查询结果:
在这里插入图片描述
这条查询语句充分体现了 SQL 在处理有序集合时的原理,这里详细地解说一下。对于这个查询的要点,我们分成两个步骤来理解更容易一些。
第一步:通过自连接生成起点和终点的组合
就这条 SQL 语句而言,具体指的是 S2.seat = S1.seat + (:head_cnt-1) 的部分。这个条件排除掉了像1~8、2~3这样长度不是3的组合,从而保证结果中出现的只有从起点到终点刚好包含 3 个空位的序列。
第二步:描述起点到终点之间所有的点需要满足的条件
决定了起点和终点以后,我们需要描述一下内部各个点需要满足的条件。为此,我们增加一个在起点和终点之间移动的所有点的集合(即以上SQL 中的 S3)。限定移动范围时使用 BETWEEN 谓词很方便。
在SQL 中遇到需要全称量化的问题时,一般的思路都是把“所有行都满足条件 P”转换成它的双重否定——不存在不满足条件 P 的行。“肯定⇔ 双重否定”之间的等价转换是使用 SQL 进行全称量化时的必备技巧,请熟练掌握。

订票三连坐(座位分排,5个座位一排)

新增座位行,如下:
在这里插入图片描述
所以,针对该结构,所需要的三连坐结果如下:
在这里插入图片描述
目前满足条件的结果是:

●  3 ~ 5
●  8 ~ 10
●  11 ~ 13

SQL语句:

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 ) 
);

查询结果:
在这里插入图片描述

单调递增和单调递减

假如存在某公司股价波动情况如下图:
在这里插入图片描述
这里,我们求一下股价单调递增的时间区间。从上表来看,目标结果是下面两个。

● 2007-01-06 ~ 2007-01-08
● 2007-01-14 ~ 2007-01-17

按照前面讲过的基本方法,首先进行第一步——通过自连接生成起点和终点的组合。

-- 生成起点和终点的组合的 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;

结果的行数较多,一共有 28 个组合,因此这里不再展示。接下来我们排除掉不符合条件的组合,即进行第二步——描述起点和终点之间的所有点需要满足的条件。

-- 求单调递增的区间的 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能处理各种数学上的问题,对于我这个数学不好的程序员来说,确实很激励我,让我对数学有了新的认识和学习的兴趣。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值