写在前面

SQL本身是作为一种数据提取工具而出现,使用SQL生成各种定制化报表和非定制化报表并非SQL原本用途的功能,但这并不意味着SQL无法实现这些功能。

用外连接进行行列转换(1)(行 → 列):制作交叉表

-- 建表语句
/* 用外连接进行行列转换(1)(行→列):制作交叉表 */
CREATE TABLE Courses
(name VARCHAR(32),
course VARCHAR(32),
PRIMARY KEY(name, course)); INSERT INTO Courses VALUES('赤井', 'SQL入门');
INSERT INTO Courses VALUES('赤井', 'UNIX基础');
INSERT INTO Courses VALUES('铃木', 'SQL入门');
INSERT INTO Courses VALUES('工藤', 'SQL入门');
INSERT INTO Courses VALUES('工藤', 'Java中级');
INSERT INTO Courses VALUES('吉田', 'UNIX基础');
INSERT INTO Courses VALUES('渡边', 'SQL入门');
-- 水平展开求交叉表(1):使用外连接
SELECT C0.name,
CASE WHEN C1.name IS NOT NULL THEN '○' ELSE NULL END AS “SQL入门",
CASE WHEN C2.name IS NOT NULL THEN '○' ELSE NULL END AS "UNIX基础",
CASE WHEN C3.name IS NOT NULL THEN '○' ELSE NULL END AS "Java中级"
FROM (SELECT DISTINCT name FROM Courses) AS C0
LEFT JOIN (SELECT name FROM Courses WHERE course = 'SQL入门') AS C1 ON C0.name = C1.name
LEFT JOIN (SELECT name FROM Courses WHERE course = 'UNIX基础') AS C2 ON C0.name = C2.name
LEFT JOIN (SELECT name FROM Courses WHERE course = 'Java中级') AS C3 ON C0.name = C3.name;
-- 评价:方法直观并容易理解,但大量用到内嵌视图和连接操作,随着列数增加,代码会臃肿,性能也会恶化
-- 水平展开求交叉表(2):使用标量子查询
SELECT C0.name,
(SELECT '○' FROM Courses AS C1 WHERE course = 'SQL入门' AND C1.name = C0.name) AS "SQL入门",
(SELECT '○' FROM Courses AS C2 WHERE course = 'UNIX基础' AND C2.name = C0.name) AS "UNIX基础",
(SELECT '○' FROM Courses AS C3 WHERE course = 'Java中级' AND C3.name = C0.name) AS "Java中级"
FROM (SELECT DISTINCT name FROM Courses) AS C0;
-- 在增加课程时,只需要修改SELECT子句后面的内容,代码修改比较简单,应对需求变更比较理想,缺点是性能不好,在SELECT子句中使用标量子查询和关联子查询时,计算开销较大
-- 水平展开求交叉表(3):嵌套使用CASE WHEN表达式
SELECT name,
CASE WHEN SUM(CASE WHEN course = 'SQL入门' THEN 1 ELSE 0 END) = 1 THEN '○' ELSE NULL END AS "SQL入门",
CASE WHEN SUM(CASE WHEN course = 'UNIX基础' THEN 1 ELSE 0 END) = 1 THEN '○' ELSE NULL END AS "UNIX基础",
CASE WHEN SUM(CASE WHEN course = 'Java中级' THEN 1 ELSE 0 END) = 1 THEN '○' ELSE NULL END AS "Java中级"
FROM Courses
GROUP BY name;

用外连接进行行列转换(2)(列 → 行):汇总重复项于一列

-- 建表语句
/* 用外连接进行行列转换(2)(列→行):汇总重复项于一列 */
CREATE TABLE Personnel
(employee varchar(32),
child_1 varchar(32),
child_2 varchar(32),
child_3 varchar(32),
PRIMARY KEY(employee)); INSERT INTO Personnel VALUES('赤井', '一郎', '二郎', '三郎');
INSERT INTO Personnel VALUES('工藤', '春子', '夏子', NULL);
INSERT INTO Personnel VALUES('铃木', '夏子', NULL, NULL);
INSERT INTO Personnel VALUES('吉田', NULL, NULL, NULL);
-- 列数据转行数据(不去除空行)
SELECT employee,child_1 FROM Personnel
UNION ALL
SELECT employee,child_2 FROM Personnel
UNION ALL
SELECT employee,child_3 FROM Personnel;
-- 列数据转行数据(去除空行)
SELECT employee,child_1 FROM Personnel WHERE child_1 IS NOT NULL
UNION ALL
SELECT employee,child_2 FROM Personnel WHERE child_2 IS NOT NULL
UNION ALL
SELECT employee,child_3 FROM Personnel WHERE child_3 IS NOT NULL;
-- 按需自定义保留记录(不完全去除重复行)

-- 准备所有孩子的视图
CREATE VIEW Children(child) AS
SELECT child_1 FROM Personnel
UNION SELECT child_2 FROM Personnel
UNION SELECT child_3 FROM Personnel; -- 获取员工子女列表的SQL语句(没有孩子的员工也要输出)
SELECT Personnel.employee,Children.child
FROM Personnel
LEFT JOIN Children
ON Children.child IN (Personnel.child_1,Personnel.child_2,Personnel.child_3);

在交叉表里制作嵌套式表侧栏

/* 在交叉表里制作嵌套式表侧栏 */
CREATE TABLE TblSex
(sex_cd char(1),
sex varchar(5),
PRIMARY KEY(sex_cd)); CREATE TABLE TblAge
(age_class char(1),
age_range varchar(30),
PRIMARY KEY(age_class)); CREATE TABLE TblPop
(pref_name varchar(30),
age_class char(1),
sex_cd char(1),
population integer,
PRIMARY KEY(pref_name, age_class,sex_cd)); INSERT INTO TblSex (sex_cd, sex ) VALUES('m', '男');
INSERT INTO TblSex (sex_cd, sex ) VALUES('f', '女'); INSERT INTO TblAge (age_class, age_range ) VALUES('1', '21岁~30岁');
INSERT INTO TblAge (age_class, age_range ) VALUES('2', '31岁~40岁');
INSERT INTO TblAge (age_class, age_range ) VALUES('3', '41岁~50岁'); INSERT INTO TblPop VALUES('秋田', '1', 'm', 400 );
INSERT INTO TblPop VALUES('秋田', '3', 'm', 1000 );
INSERT INTO TblPop VALUES('秋田', '1', 'f', 800 );
INSERT INTO TblPop VALUES('秋田', '3', 'f', 1000 );
INSERT INTO TblPop VALUES('青森', '1', 'm', 700 );
INSERT INTO TblPop VALUES('青森', '1', 'f', 500 );
INSERT INTO TblPop VALUES('青森', '3', 'f', 800 );
INSERT INTO TblPop VALUES('东京', '1', 'm', 900 );
INSERT INTO TblPop VALUES('东京', '1', 'f', 1500 );
INSERT INTO TblPop VALUES('东京', '3', 'f', 1200 );
INSERT INTO TblPop VALUES('千叶', '1', 'm', 900 );
INSERT INTO TblPop VALUES('千叶', '1', 'f', 1000 );
INSERT INTO TblPop VALUES('千叶', '3', 'f', 900 );
-- 构造侧边栏
SELECT age_class,age_range,sex_cd,sex
FROM TblAge CROSS JOIN TblSex;
-- 处理TblPop表
SELECT age_class,sex_cd,
SUM(CASE WHEN pref_name IN ('秋田','青森') THEN population ELSE 0 END) AS "东北",
SUM(CASE WHEN pref_name IN ('东京','千叶') THEN population ELSE 0 END) AS "关东"
FROM TblPop
GROUP BY age_class,sex_cd;
-- 连接两张表
SELECT A.age_range,A.sex,B.东北,B.关东
FROM (SELECT age_class,age_range,sex_cd,sex FROM TblAge CROSS JOIN TblSex) AS A
LEFT JOIN (SELECT age_class,sex_cd,
SUM(CASE WHEN pref_name IN ('秋田','青森') THEN population ELSE 0 END) AS "东北",
SUM(CASE WHEN pref_name IN ('东京','千叶') THEN population ELSE 0 END) AS "关东"
FROM TblPop
GROUP BY age_class,sex_cd) AS B
ON A.age_class = B.age_class and A.sex_cd = B.sex_cd;

作为乘法运算的连接

-- 建表语句
/* 作为乘法运算的连接 */
CREATE TABLE Items
(item_no INTEGER PRIMARY KEY,
item VARCHAR(32) NOT NULL); INSERT INTO Items VALUES(10, 'FD');
INSERT INTO Items VALUES(20, 'CD-R');
INSERT INTO Items VALUES(30, 'MO');
INSERT INTO Items VALUES(40, 'DVD'); CREATE TABLE SalesHistory
(sale_date DATE NOT NULL,
item_no INTEGER NOT NULL,
quantity INTEGER NOT NULL,
PRIMARY KEY(sale_date, item_no)); INSERT INTO SalesHistory VALUES('2007-10-01', 10, 4);
INSERT INTO SalesHistory VALUES('2007-10-01', 20, 10);
INSERT INTO SalesHistory VALUES('2007-10-01', 30, 3);
INSERT INTO SalesHistory VALUES('2007-10-03', 10, 32);
INSERT INTO SalesHistory VALUES('2007-10-03', 30, 12);
INSERT INTO SalesHistory VALUES('2007-10-04', 20, 22);
INSERT INTO SalesHistory VALUES('2007-10-04', 30, 7);
-- 要求得到所有item即便没有销售记录的quantity求和
-- 方法一:连接前聚合,然后一对一进行连接
SELECT Items.item_no,SH.quantity
FROM Items LEFT JOIN (
SELECT item_no,sum(quantity) as quantity
FROM SalesHistory
GROUP BY item_no) AS SH
ON Items.item_no = SH.item_no;
-- 评价:从性能角度讲,通过聚合将SH上的item_no变成了非空不重复,但SH本身不存在主键索引,无法利用查询优化
-- 方法二:一对多进行连接不会增加行
SELECT Items.item_no,sum(SH.quantity) AS quantity
FROM Items
LEFT JOIN SalesHistory AS SH
ON Items.item_no = SH.item_no
GROUP BY Items.item_no;
-- 评价:这种方法没有使用临时视图,性能会有所改善

全外连接

面向集合的角度理解连接类型

  • 左外连接 LEFT OUTER JOIN
  • 右外连接 RIGHT OUTER JOIN
  • 全外连接 FULL OUTER JOIN
/* 全外连接 */
CREATE TABLE Class_A
(id char(1),
name varchar(30),
PRIMARY KEY(id)); CREATE TABLE Class_B
(id char(1),
name varchar(30),
PRIMARY KEY(id)); INSERT INTO Class_A (id, name) VALUES('1', '田中');
INSERT INTO Class_A (id, name) VALUES('2', '铃木');
INSERT INTO Class_A (id, name) VALUES('3', '伊集院'); INSERT INTO Class_B (id, name) VALUES('1', '田中');
INSERT INTO Class_B (id, name) VALUES('2', '铃木');
INSERT INTO Class_B (id, name) VALUES('4', '西园寺');
-- 全外连接保留全部信息
SELECT
COALESCE(A.id,B.id) AS id,A.name AS a_name,B.name AS b_name
FROM Class_A AS a FULL JOIN Class_B AS b
ON A.id = B.id; -- 数据库不支持全外连接时的替代方案
SELECT A.id AS id,A.name,B.name FROM Class_A AS A LEFT JOIN Class_B AS B ON A.id = B.id
UNION
SELECT B.id AS id,A.name,B.name FROM Class_A AS A RIGHT JOIN Class_B AS B ON A.id = B.id;

INNER JOIN(或者INTERSECT)相当于交集,UNION(LEFT JOIN AND RIGHT JOIN ,再或者FULL JOIN)相当于并集

用外连接进行集合运算

用外连接求差集:A-B

SELECT A.id,A.name AS A_name,B.name AS B_name FROM Class_A AS A LEFT JOIN Class_B AS B ON A.id = B.id WHERE B.name IS NULL;

用外连接求差集:B-A

SELECT A.id,A.name AS A_name,B.name AS B_name FROM Class_A AS A RIGHT JOIN Class_B AS B ON A.id = B.id WHERE A.name IS NULL;

用全外连接求异或集

SELECT COALESCE(A.id,B.id) AS id,COALESCE(A.name,B.name) AS name FROM Class_A AS A FULL JOIN Class_B AS B ON A.id = B.id WHERE A.name IS NULL OR B.name IS NULL;

本节小结

  • SQL不是用来生成报表的语言,不建议用齐进行格式转换
  • 必要时可以考虑外连接和CASE表达式来解决问题
  • 生成嵌套表侧栏时,如果先生成主表的笛卡尔积再进行连接,很容易就可以完成
  • 从行数来看,表连接可以看成乘法。因此,当表之间是一对多的关系时,连接后行数不会增加
  • 外连接的思想和集合运算很像,使用外连接可以实现各种集合运算

练习题

-- 练习题 1-5-1 :先连接还是先聚合
SELECT master.age_range,master.sex,
SUM(CASE WHEN data.pref_name IN ('青森','秋田') THEN population ELSE NULL END) AS "东北",
SUM(CASE WHEN data.pref_name IN ('东京','千叶') THEN population ELSE NULL END) AS "关东"
FROM (TblAge CROSS JOIN TblSex) AS master
LEFT JOIN TblPop AS data
ON master.age_class = data.age_class
AND master.sex_cd = data.sex_cd
GROUP BY master.age_range,master.sex;
-- 练习题 1-5-2:请留意孩子的人数
SELECT Personnel.employee,COUNT(Children.child)
FROM Personnel
LEFT JOIN Children
ON Children.child IN (Personnel.child_1,Personnel.child_2,Personnel.child_3)
GROUP BY Personnel.employee;
-- 练习题 1-5-3 全外连接和merge运算符
MERGE INTO Class_A A
USING (SELECT *
FROM Class_B ) B
ON (A.id = B.id)
WHEN MATCHED THEN
UPDATE SET A.name = B.name
WHEN NOT MATCHED THEN
INSERT (id, name) VALUES (B.id, B.name);

SQL进阶系列之5外连接的用法的更多相关文章

  1. 外连接的用法 -- 《SQL进阶教程》 jupyter note

    import pandas as pd import sqlite3 conn = sqlite3.connect('1-5.db') 用外连接进行行列转换1(行 -> 列): 制作交叉表 怎么 ...

  2. Sql Server系列:多表连接查询

    连接查询是关系数据中最主要的查询,包括内连接.外连接等.通过连接运算符可以实现多个表查询.内连接查询操作列出与连接条件匹配的数据行,它使用比较运算符比较被连接列的列值.SQL Server中的内连接有 ...

  3. SQL进阶系列之9用SQL处理数列

    写在前面 关系模型的数据结构里,并没有顺序的概念,但SQL处理有序集合也有坚实的理论基础 生成连续编号 --生成连续编号 CREATE TABLE Digits (digit INTEGER PRIM ...

  4. SQL进阶系列之6用关联子查询比较行与行

    写在前面 使用SQL对同一行数据进行列间的比较很简单,只需要在WHERE子句里写上比较条件就可以了,对于不同行数据进行列间比较需要使用自关联子查询. 增长.减少.维持现状 需要用到行间比较的经典场景是 ...

  5. SQL进阶系列之2自连接

    写在前面 一般地,SQL的连接运算根据其特征的不同,有着不同的名称,比如内连接.外连接.交叉连接等,这些连接大多是以不同的表或视图为对象进行的,针对相同的表进行的连接成为自连接.理解自连接有助于我们理 ...

  6. Oracle学习笔记:外连接(+)的用法

    Oracle中常用 left join 和 right join 来进行外连接,同时,oracle也支持 (+) 的特殊用法,也是表示外连接,并且总是放在非主表的一方. 例如: 左外连接: selec ...

  7. Mysql SQL优化系列之——执行计划连接方式浅释

    关系库SQL调优中,虽然思路都是一样的,具体方法和步骤也是大同小异,但细节却不容忽视,尤其是执行计划的具体细节的解读中,各关系库确实有区别,特别是mysql数据库,与其他关系库的差别更大些,下面,我们 ...

  8. SQL进阶系列之12SQL编程方法

    写在前面 KISS -- keep it sweet and simple 表的设计 注意命名的意义 英文字母 + 阿拉伯数字 + 下划线"_" 属性和列 编程的方针 写注释 注意 ...

  9. SQL进阶系列之11让SQL飞起来

    写在前面 SQL的性能优化是数据库使用者必须面对的重要问题,本节侧重SQL写法上的优化,SQL的性能同时还受到具体数据库的功能特点影响,这些不在本节讨论范围之内 使用高效的查询 参数是子查询时,使用E ...

随机推荐

  1. 图片转化base64格式

    public function Base64EncodeImage($ImageFile) { // 图片转化base64格式 , 图片需要在本地,有访问权限 , 相对于项目路径 if(file_ex ...

  2. Appium脚本(4) 使用uiautomator方法定位元素

    from app.find_element.capability import driver from time import sleep # 使用uiautomator方法定位元素 accunt_i ...

  3. XT交易所Websocket API

    WebSocketAPI xt为用户提供了一个简单的而又强大的API,旨在帮助用户快速高效的将xt交易功能整合到自己应用当中. WebSocket服务地址 xt WebSocket服务连接地址:wss ...

  4. iperf—流量测试

    iperf是另外一款用于流量测试的软件,主要运行于Windows系统和安卓系统的手机/PAD(IOS系统下载需要收费). 一个工作在Server模式,另外一个工作在Client模式,输入Server的 ...

  5. centos个性化命令行提示符

    为了在满屏的命令中找到用户的命令行,所以很有必要设置一种字体颜色.我就设置最实用的一种,可以用蓝色字体显示当前所在路径 命令行输入: echo "PS1='[\${debian_chroot ...

  6. Delphi文字转语音TTS【支持选择语音库,播放,暂停,开始,停止,生成语音文件,设置音量,设置语速】

    作者QQ:(648437169) 点击下载➨文字转语音TTS [Delphi 文字转语音TTS]调用系统自带的TTS组件,支持XP,vista,win7,win8,win10系统,支持选择语音库,播放 ...

  7. Python实现斐波那契递归和尾递归计算

    ##斐波那契递归测试 def fibonacciRecursive(deepth): if deepth == 1: return 1 elif deepth == 2: return 1 else: ...

  8. windwos源码安装mysql

    进入官网下载相关的mysql安装文件,解压到指定目录如E:\mysql-5.7.23-winx64下,然后进入该目录下新建data文件夹与my.ini文件,在my.ini文件中 [mysqld] po ...

  9. Linux环境下进程的CPU占用率

    阿里云服务器网站:https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=qqwovx6h 文字来源:http://www.s ...

  10. C# 练习题 将一个正整数分解质因数

    题目:将一个正整数分解质因数.例如:输入90,打印出90=2*3*3*5.程序分析:对n进行分解质因数,应先找到一个最小的质数k,然后按下述步骤完成:(1)如果这个质数恰等于n,则说明分解质因数的过程 ...