前言
使用 SQL 对同一行数据进行列间的比较很简单,只需要在 WHERE 子句里写上比较条件就可以了,例如 col_1 = col_2 。但是,对不同行数据进行列间的比较却没那么简单。使用 SQL 进行行间比较时,发挥主要作用的技术是关联子查询
用列表展示与上一年的比较结果
查询和去年相比, 销售额的趋势
使用子查询
SELECT s.`year`,s.sale,
case
when s.sale = (select sale from sales where year = s.`year`-1) then '持平'
when s.sale > (select sale from sales where year = s.`year`-1) then '上升'
when s.sale < (select sale from sales where year = s.`year`-1) then '下降'
else '-' end as '趋势'
from sales s;
使用自连接. 采用这种实现方法时,由于这里没有 1990 年之前的数据,所以 1990年会被排除掉,执行结果会少一行。
SELECT S1.year, S1.sale,
CASE WHEN S1.sale = S2.sale THEN '→'
WHEN S1.sale > S2.sale THEN '↑'
WHEN S1.sale < S2.sale THEN '↓'
ELSE ' — ' END AS var
FROM Sales S1, Sales S2
WHERE S2.year = S1.year - 1
ORDER BY year;
上面是书上的答案, 看起来不顺, 理一理, 先内连接
看清楚字段再判断:
时间轴有间断时:和过去最临近的时间进行比较
有年份缺失, 这样一来,“年份 - 1 ”这个条件就不能用了。我们需要把它扩展成更普遍的情况,用某一年的数据和它过去最临近的年份进行比较。
查询与过去最临近的年份营业额相同的年份
-- 子查询
SELECT year, sale FROM Sales2 S1
WHERE sale =
(SELECT sale FROM Sales2 S2
WHERE S2.year =
(SELECT MAX(year) -- 条件2 :在满足条件1 的年份中,年份最早的一个
FROM Sales2 S3 WHERE S1.year > S3.year)) -- 条件1 :与该年份相比是过去的年份
ORDER BY year;
-- 自连接,可以减少一层子查询的嵌套。
SELECT S1.year AS year,
S1.year AS year
FROM Sales2 S1, Sales2 S2 WHERE S1.sale = S2.sale
AND S2.year = (SELECT MAX(year)
FROM Sales2 S3 WHERE S1.year > S3.year)
ORDER BY year;
移动累计值和移动平均值
求截止到那个时间点的账户余额
-- 求累计值:使用窗口函数
SELECT prc_date, prc_amt,
SUM(prc_amt) OVER (ORDER BY prc_date) AS onhand_amt
FROM Accounts;
-- 求累计值:使用冯· 诺依曼型递归集合
select a.prc_date,a.prc_amt,
(select sum(b.prc_amt)
from accounts b where a.prc_date>=b.prc_date
) as onhand_amount
from accounts a;
结果
-- 求移动累计值(1):使用窗口函数
SELECT prc_date, prc_amt,
SUM(prc_amt) OVER (ORDER BY prc_date
ROWS 2 PRECEDING) AS onhand_amt
FROM Accounts;
-- 求移动累计值(2):不满3 行的时间区间也输出
select a.prc_date,a.prc_amt,
(select sum(b.prc_amt)
from accounts b where a.prc_date>=b.prc_date
and (select count(*) from accounts c where c.prc_date BETWEEN b.prc_date and a.prc_date) <=3
) as onhand_amount
from accounts a;
查询重叠的区间(带续)
灰色是有重叠的部分
SELECT reserver, start_date, end_date
FROM Reservations R1
WHERE EXISTS
(SELECT *
FROM Reservations R2
WHERE R1.reserver <> R2.reserver
AND (
( R1.start_date BETWEEN R2.start_date AND R2.end_date
OR R1.end_date BETWEEN R2.start_date AND R2.end_date)
OR
( R2.start_date BETWEEN R1.start_date AND R1.end_date
AND R2.end_date BETWEEN R1.start_date AND R1.end_date)
));
测试结果
本章小结
- 作为面向集合语言,SQL 在比较多行数据时,不进行排序和循环。
- SQL 的做法是添加比较对象数据的集合,通过关联子查询(或者自连接)一行一行地偏移处理。如果选用的数据库支持窗口函数,也可以考虑使用窗口函数。
- 求累计值和移动平均值的基本思路是使用冯·诺依曼型递归集合。
- 关联子查询的缺点是性能及代码可读性不好。
- 人生中不可能所有的事情都一帆风顺。