SQL进阶系列之6用关联子查询比较行与行
写在前面
使用SQL对同一行数据进行列间的比较很简单,只需要在WHERE子句里写上比较条件就可以了,对于不同行数据进行列间比较需要使用自关联子查询。
增长、减少、维持现状
需要用到行间比较的经典场景是时间序列分析
-- 建表语句
-- 增长、减少、维持现状
CREATE TABLE Sales
(year INTEGER NOT NULL ,
sale INTEGER NOT NULL ,
PRIMARY KEY (year));
INSERT INTO Sales VALUES (1990, 50);
INSERT INTO Sales VALUES (1991, 51);
INSERT INTO Sales VALUES (1992, 52);
INSERT INTO Sales VALUES (1993, 52);
INSERT INTO Sales VALUES (1994, 50);
INSERT INTO Sales VALUES (1995, 50);
INSERT INTO Sales VALUES (1996, 49);
INSERT INTO Sales VALUES (1997, 55);
-- 求与上一年营业额一样的年份(1):使用关联子查询
SELECT year,sale
FROM Sales AS S1
WHERE sale = (SELECT sale FROM Sales AS S2 WHERE S2.year = S1.year - 1)
ORDER BY year;
-- 求与上一年营业额一样的年份(2):使用自连接
SELECT S1.year,S1.sale
FROM Sales AS S1,Sales AS S2
WHERE S1.sale = S2.sale AND S2.year = S1.year - 1
ORDER BY year;
用列表展示与上一年的比较结果
-- 求出是增长了还是减少了,抑或是维持现状(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;
时间轴有间断时:和过去最邻近的时间进行比较
--时间轴有间断时:和过去最临近的时间进行比较
CREATE TABLE Sales2
(year INTEGER NOT NULL ,
sale INTEGER NOT NULL ,
PRIMARY KEY (year));
INSERT INTO Sales2 VALUES (1990, 50);
INSERT INTO Sales2 VALUES (1992, 50);
INSERT INTO Sales2 VALUES (1993, 52);
INSERT INTO Sales2 VALUES (1994, 55);
INSERT INTO Sales2 VALUES (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;
-- 求每一年与过去最临近年份之间的营业额之差(1):结果里不包括最临近年份
SELECT S2.year AS pre_year,S1.year AS now_year,S2.sale AS pre_sale,S1.sale AS now_sale,
S1.sale - S2.sale AS diff
FROM Sales2 AS S1,Sales2 AS S2
WHERE S2.year = (SELECT MAX(year) FROM Sales2 S3 WHERE S1.year > S3.year)
ORDER BY now_year;
-- 求每一年与过去最临近年份之间的营业额之差(2):使用自外连接,结果里包含最早的年份
SELECT S2.year AS pre_year,S1.year AS now_year,S2.sale AS pre_sale,S1.sale AS now_sale,
S1.sale - S2.sale AS diff
FROM Sales2 S1 LEFT JOIN Sales2 S2
ON S2.year = (SELECT max(year) FROM Sales2 S3 WHERE S1.year > S3.year)
ORDER BY now_year;
移动累计值和移动平均值
-- 建表语句
--移动累计值和移动平均值
CREATE TABLE Accounts
(prc_date DATE NOT NULL ,
prc_amt INTEGER NOT NULL ,
PRIMARY KEY (prc_date)) ;
INSERT INTO Accounts VALUES ('2006-10-26', 12000 );
INSERT INTO Accounts VALUES ('2006-10-28', 2500 );
INSERT INTO Accounts VALUES ('2006-10-31', -15000 );
INSERT INTO Accounts VALUES ('2006-11-03', 34000 );
INSERT INTO Accounts VALUES ('2006-11-04', -5000 );
INSERT INTO Accounts VALUES ('2006-11-06', 7200 );
INSERT INTO Accounts VALUES ('2006-11-11', 11000 );
-- 求累计值:使用窗口函数(依赖特定数据库实现,Mysql目前还不支持)
SELECT prc_date,prc_amt,SUM(prc_amt) OVER (ORDER BY prc_date) AS onhand_amt FROM Accounts;
-- 求累计值:使用冯·诺依曼递归集合
SELECT prc_date,prc_amt,(SELECT SUM(prc_amt) FROM Accounts A2 WHERE A1.prc_date >= A2.prc_date) AS onhand_amt
FROM Accounts A1
ORDER BY prc_date;
-- 求移动累计值(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,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 mvb_sum
FROM Accounts A1
ORDER BY prc_date;
-- 求移动累计值(2):不满3行的按无效处理
SELECT prc_date,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 mvb_sum
FROM Accounts A1
ORDER BY prc_date;
-- 去掉聚合并输出,便于理解
SELECT A1.prc_date AS A1_date,A2.prc_date AS A2_date,A2.prc_amt AS amt
FROM Accounts A1,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
ORDER BY A1_date,A2_date;
查询重叠的时间区间
--查询重叠的时间区间
CREATE TABLE Reservations
(reserver VARCHAR(30) PRIMARY KEY,
start_date DATE NOT NULL,
end_date DATE NOT NULL);
INSERT INTO Reservations VALUES('木村', '2006-10-26', '2006-10-27');
INSERT INTO Reservations VALUES('荒木', '2006-10-28', '2006-10-31');
INSERT INTO Reservations VALUES('堀', '2006-10-31', '2006-11-01');
INSERT INTO Reservations VALUES('山本', '2006-11-03', '2006-11-04');
INSERT INTO Reservations VALUES('内田', '2006-11-03', '2006-11-05');
INSERT INTO Reservations VALUES('水谷', '2006-11-06', '2006-11-06');
--山本的入住日期为4日时
DELETE FROM Reservations WHERE reserver = '山本';
INSERT INTO Reservations VALUES('山本', '2006-11-04', '2006-11-04');
-- 求重叠的住宿期间
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));
-- 求重叠的住宿期间:把包含别人的住宿期间的情况也输出
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 OR R2.end_date BETWEEN R1.start_date AND R1.end_date)));
小结
关联子查询的缺点:代码可读性不好,难以理解;性能不好,尤其是使用SELECT子句中使用标量子查询时
- SQL是面向集合的语言,比较多行时不进行排序和循环
- SQL的做法是添加比较对象数据的集合,通过关联子查询或自连接一行一行的处理。也可以参考窗口函数
- 求累计值和平均值的基本思路是使用冯·诺依曼型递归集合
练习题
/* 练习题1-6-1:简化多行数据的比较*/
SELECT S1.year, S1.sale,
CASE SIGN(sale -
(SELECT sale
FROM Sales S2
WHERE S2.year = S1.year - 1) )
WHEN 0 THEN '→' /* 持平 */
WHEN 1 THEN '↑' /* 增长 */
WHEN -1 THEN '↓' /* 减少 */
ELSE '—' END AS var
FROM Sales S1
ORDER BY year;
/* 练习题1-6-2:使用OVERLAPS查询重叠的时间区间 */
SELECT reserver, start_date, end_date
FROM Reservations R1
WHERE EXISTS
(SELECT *
FROM Reservations R2
WHERE R1.reserver <> R2.reserver /* 与除自己以外的客人进行比较 */
AND (R1.start_date, R1.end_date) OVERLAPS (R2.start_date, R2.end_date));
/* 练习题1-6-2:使用OVERLAPS查询重叠的时间区间 */
SELECT R1.reserver, R1.start_date, R1.end_date
FROM Reservations R1, Reservations R2
WHERE R1.reserver <> R2.reserver /* 与除自己以外的客人进行比较 */
AND (R1.start_date, R1.end_date) OVERLAPS (R2.start_date, R2.end_date);
SQL进阶系列之6用关联子查询比较行与行的更多相关文章
- SQL进阶系列之7用SQL进行集合运算
写在前面 集合论是SQL语言的根基,因为这种特性,SQL也被称为面向集合语言 导入篇:集合运算的几个注意事项 注意事项1:SQL能操作具有重复行的集合(multiset.bag),可以通过可选项ALL ...
- SQL进阶系列之5外连接的用法
写在前面 SQL本身是作为一种数据提取工具而出现,使用SQL生成各种定制化报表和非定制化报表并非SQL原本用途的功能,但这并不意味着SQL无法实现这些功能. 用外连接进行行列转换(1)(行 → 列): ...
- SQL进阶系列之2自连接
写在前面 一般地,SQL的连接运算根据其特征的不同,有着不同的名称,比如内连接.外连接.交叉连接等,这些连接大多是以不同的表或视图为对象进行的,针对相同的表进行的连接成为自连接.理解自连接有助于我们理 ...
- 如何正确理解SQL关联子查询
一.基本逻辑 对于外部查询返回的每一行数据,内部查询都要执行一次.在关联子查询中是信息流是双向的.外部查询的每行数据传递一个值给子查询,然后子查询为每一行数据执行一次并返回它的记录.然后,外部查询根据 ...
- SQL关联子查询
SQL关联子查询执行顺序: 1.先取到主查询中的相关数据,一次取一行主查询的数据 2.然后传入子查询,进行子查询 3.最后做主查询where筛选,注意子查询的where条件同样需要加在主查询后 参考: ...
- 一文让你彻底理解SQL关联子查询
员工表的主要信息: 需求:检索工资大于同职位的平均工资的员工信息. 直觉的做法 员工多,而相应的职位(如销售员.经理.部门经理等)少,因此首先想到的思路是对职位分组,这样就能分别得到各个职位的平均工资 ...
- mssql sql高效关联子查询的update 批量更新
/* 使用带关联子查询的Update更新 --1.创建测试表 create TABLE Table1 ( a varchar(10), b varchar(10), ...
- SQL基础教程(第2版)第5章 复杂查询:5-3 关联子查询
第5章 复杂查询:5-3 关联子查询 ● 关联子查询会在细分的组内进行比较时使用.● 关联子查询和GROUP BY子句一样,也可以对表中的数据进行切分.● 关联子查询的结合条件如果未出现在子查询之中就 ...
- 利用带关联子查询Update语句更新数据
Update是T-sql中再简单不过的语句了,update table set column=expression [where condition],我们都会用到.但update的用法不仅于此,真 ...
随机推荐
- EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器解决方案之点播分享
背景介绍 EasyDSS流媒体服务器软件,提供一站式的视频上传.转码.点播.直播.时移回放等服务,极大地简化了开发和集成的工作.其中,点播功能主要包含:上传.转码.分发.直播功能,主要包含:直播.录像 ...
- Docker是什么?
Docker是什么? Docker是一个虚拟环境容器,可以将你的环境.代码.配置文件等一并打包到这个容器中,并发布和应用到任意平台中.比如,你在本地部署了git,jenkins等,可以将其与插件一并打 ...
- 使用自定义注解和AOP管理shiro权限
一.场景 在使用shiro框架的时候,遇到了这样的需求:本系统有多个用户,每个用户分配不同角色,每个角色的权限也不一致.比如A用户拥有新闻列表的增删改查权限,而B用户只有查看新闻列表的权限,而没有删除 ...
- Centos7时区修改方法汇总
方法一: timedatectl set-timezone Asia/Shanghai 方法二: 设置环境变量TZ(这个方法用得比较少,但是有一次就是这个方法帮了我大忙,其他都无法实现修改时区,特此记 ...
- javascript中var、let、const的区别
这几天修改别人的js,发现声明变量有的用var,有的用let,那它们有什么区别呢? javascript中声明变量的方式有:var.let.const 1.var (1)作用域: 整个函数范围内,或者 ...
- php imagick添加文字和图片的方法
Imagick添加文字和图片的方法<pre><?php $image = new Imagick(); $image->readImage("/home/www/wo ...
- visual studio ------- 更改字体和背景颜色
1.打开vs 点击工具 选择选项 2.想要更换主题的也可以更换主题, 3.更改字体 4.更改为护眼小背景 参数为 85 123 205 ee
- springboot结合日志门面SLF4j和日志实现Logback的使用
一.此处主要介绍在springboot工程下如何使用 logback + slf4j 进行日志记录. logback主要包含三个组成部分:Loggers(日志记录器).Appenders(输出目的在 ...
- 未安装发布所需的web发布扩展
解决方案:需要安装web deploy 下载网站:https://www.iis.net/downloads/microsoft/web-deploy 假如还是打不开的话,估计时打开方式错误了, 要用 ...
- python学习-33 max和min函数的高级使用
1.简单比较 age_dic={'age1456':15,'age2':16,'xiaohong_age':12,'xiaoming_age4':18,'age5':10} print(max(age ...