SQL进阶教程——用关联子查询比较行与行(第六章)

1. 前言

使用SQL对同一行数据进行列间比较很简单,只需要在WHERE子句里写上比较条件就可以了。但是,对不同行数据进行列间的比较却没那么简单。然而,这并不是说我们不能用SQL进行行与行之间的比较。
使用SQL进行行间比较时,发挥主要作用的技术是关联子查询,特别是与自连接结合的“关联子查询”。

2. 用列表展示与上一年的比较结果

  • Sales
year(年份) sale(年营业额:亿日元)
1990 50
1991 51
1992 52
1993 52
1994 50
1995 50
1996 49
1997 55
-- 求出是增长了还是减少了,抑或是维持现状(1):使用关联子查询
SELECT S1.year,S1.sale,
CASE WHEN S1.sale < (SELECT sale FROM Sales AS S2 WHERE S2.year = S1.year - 1) THEN '↓'
     WHEN S1.sale = (SELECT sale FROM Sales AS S2 WHERE S2.year = S1.year - 1) THEN '→'
     WHEN S1.sale > (SELECT sale FROM Sales AS S2 WHERE S2.year = S1.year - 1) THEN '↑'
     ELSE '--' END AS var
FROM Sales AS S1
ORDER BY S1.year;

注意:在关联子查询中,对于外部查询返回的每一行数据,内部查询都要执行一次。另外,在关联子查询中是信息流是双向的。外部查询的每行数据传递一个值给子查询,然后子查询为每一行数据执行一次并返回它的记录。然后,外部查询根据返回的记录做出决策。

-- 求出是增长了还是减少了,抑或是维持现状(2):使用自连接查询(最早的年份不会出现在结果里)
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 AS S1,Sales AS S2 
WHERE S2.year = S1.year - 1;

3. 时间轴有断点时:和过去最近的时间进行比较

对某一年,“过去最临近的年份”需要满足下面两个条件

  1. 与该年份相比是过去的年份
  2. 在满足条件1的年份中,年份最早的一个
  • Sale2:有年份缺失
year(年份) sale(年营业额)
1990 50
1992 50
1993 52
1994 55
1997 55
-- 查询与过去最邻近的年份营业额相同的年份
SELECT year,sale
FROM Sales2 S1
WHERE sale = (SELECT sale
			  FROM Sales S2 
			  WHERE S2.year = (SELECT max(S3.year) 
			  					FROM Sales2 AS S3 
			  					WHERE S1.year > S3.year ))
ORDER BY year;

-- 查询与过去最邻近的年份营业额相同的年份:同时使用自连接
SELECT S1.year AS year,S1.sale AS sale
FROM Sales2 AS S1,Sales2 AS S2
WHERE S1.sale = S2.sale AND S2.year = 
(SELECT MAX(year) FROM Sales2 S3 WHERE S1.year > S3.year)
ORDER BY year;

注意:再次强调,不清楚请按照SQL内部执行流程在纸上来一遍(关联子查询是从外部开始,将每一条记录带入内部然后返回)

4. 移动累计值和移动平均值

  • Accounts
prc_date(处理日期) prc_amt(处理金额)
2006-10-26 12 000
2006–10-28 2 500
2006–10-31 -15 000
2006–11-03 34 000
2006–11-04 -5 000
2006–11-06 7 200
2006–11-11 11 000
  • 求移动累计值
--使用窗口函数
SELECT prc_date,prc_amt, sum(prc_amt) over(order by prc_date) AS  onhand_amt
FROM accounts;

---求累计值,关联子查询
SELECT a1.prc_date,(SELECT sum(a2.prc_amt) 
	FROM accounts a2 WHERE a2.prc_date  < a1.prc_date)  AS  onhand_amt
FROM accounts as a1
ORDER BY a1.prc_date;
  • 求移动平均值
prc_date(处理日期) prc_amt(处理金额)
2006-10-26 12 000
2006–10-28 2 500
2006–10-31 -15 000
2006–11-03 34 000
2006–11-04 -5 000
2006–11-06 7 200
2006–11-11 11 000
-- 求移动累计值(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 prc_date, A1.prc_amt,
( SELECT SUM(prc_amt) FROM Accounts A2 
  WHERE A1.prc_date >= A2.prc_date
  AND (SELECT COUNT(*) FROM Accounts A3 WHERE A3.prc_date BETWEEN A2.prc_date AND A1.prc_date ) <= 3 
) AS mvg_sum
FROM Accounts A1
-- 移动累计值(3):不满3行的区间按无效处理
SELECT prc_date, A1.prc_amt,
( SELECT SUM(prc_amt) FROM Accounts A2 WHERE A1.prc_date >= A2.prc_date
  AND (SELECT COUNT(*) FROM Accounts A3 WHERE A3.prc_date BETWEEN A2.prc_date AND A1.prc_date ) <= 3
  HAVING COUNT(*) =3
) AS mvg_sum -- 不满3 行数据的不显示
FROM Accounts A1
ORDER BY prc_date;
--这里不能直接将“<=3”改成“=3”,会使不满三行的数据完全不显示,而不是不显示SUM列

5. 查询重叠的时间区间

  • Reservations
reserver(入住客人) start_date(入住日期) end_date(离店日期)
木村 2006-10-26 2006-10-27
荒木 2006-10-28 2006-10-31
2006-10-31 2006-11-01
山本 2006-11-03 2006-11-04
内田 2006-11-03 2006-11-05
水谷 2006-11-06 2006-11-06
  • 日期的重叠类型
  1. 自己入住日期在他人的住宿期间内
  2. 自己的离店日期在他人的住宿期间内
  3. 自己的入住日期的离店日期都在他人的住宿期间内
  • 如何查处住宿日期重叠的客人并列表表示
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-- 条件(1):自己的入住日期在他人的住宿期间内
	OR R1.end_date BETWEEN R2.start_date AND R2.end_date)-- 条件(2):自己的离店日期在他人的住宿期间内
);
--关联子查询存在记录,就返回相应的记录
  • 升级版:把完全包含别人的住宿期间的情况也输出
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-- 条件(1):自己的入住日期在他人的住宿期间内
	OR R1.end_date BETWEEN R2.start_date AND R2.end_date)-- 条件(2):自己的离店日期在他人的住宿期间内
	OR (R2.start_date BETWEEN R1.start_date AND R1.end_date
		AND
		R2.end_date BETWEEN R1.start_date AND R1.end_date)--条件(3):自己的住宿期间完全包含了别人的住宿期间
);

本节要点:

  1. 关联子查询代码可读性不好
  2. 在SELECT子句里面使用了关联子查询时,性能可能会变差。
  3. 我们掌握任何一样东西,都像一把双刃剑,有着好和坏两面。所以请运用智慧利用它
  4. 作为面向集合语言,SQL在比较多行数据时,不进行排序和循环。
  5. SQL的做法时添加比较对象数据的集合,通过关联子查询一行一行的偏移处理。

你可能感兴趣的:(sql)