复杂的多表查询——以超市交易数据为例

之前的内容基本上都是基于单表进行的查询操作,但是在实际工作中,数据往往分散在多个表中,这个时候我们就需要用到多表查询的知识了。

通常来说,多表查询主要有两类:一类是纵向的表合并,也就是将结构相同的表上下拼接起来;另一类是横向的表连接,即将多个表中的字段合并到一张大表中。
(1)纵向表合并
纵向表合并非常好理解,就是把多张相同结构的表按照垂直的方向,将它们进行合并,直白的理解就是上下堆叠(也就是记录的追加)。下面我们以某超市的经营数据为例,来学习一下纵向表合并。

假设有一个大型连锁超市,它有很多家加盟店,各个加盟店将不同月份的经营数据存储在各自的数据表中。每家店数据的存储方式都相同,也就是字段都一样,分别为:门店ID、用户ID(如果没有办理会员,则用户ID为空)、订单ID、交易日期、应付金额、折扣金额、实付金额以及支付类型。

现在的需求是需要把A、B、C三个超市的交易记录合并到一张表里面。

纵向合并表需要用到UNION或者UNION ALL关键词,这两个关键词的功能是一样的(都是合并操作),但是还是有很大区别的:

  • UNION ALL 在合并表的时候不做任何附加动作,只是将多个表格简单的首尾相连;
  • UNION 合并表格的时候,除了拼接之外还会多一个附加动作——去重(以前旧版本还有排序功能,新版本舍弃了排序功能)

在性能方面,UNION ALL的合并速度要比UNION 快的多,尤其是数据量比较大的时候,两者的合并速度差异还是非常明显的。所以在做纵向表合并的时候,一定要考虑清楚是否需要做去重处理。

# UNION 和 UNION ALL 的区别
SELECT pay_type FROM TransC1805
UNION ALL
SELECT pay_type FROM transD1810; SELECT pay_type FROM TransC1805
UNION
SELECT pay_type FROM transD1810;

下面尝试使用UNION ALL 将三张交易表合并成一个表:

SELECT * from TransA1710
UNION ALL
SELECT * from TransB1801
UNION ALL
SELECT * FROM TransC1805;

当需要合并的表格的字段数量和顺序都一样的时候,这样写是没有问题的。如果其中有一个表的结构不一致(包括数量和排布顺序),这样写就会出问题了。比如,现在有超市D在2018年10月份的交易数据,但是这个门店在记录数据的时候,字段顺序做了一些调整:

# 如果按照上面的方式合并,得到的结果就会有问题:会按照错乱的顺序直接拼接
SELECT * FROM TransC1805
UNION ALL
SELECT * FROM transD1810;
# 正确的写法
SELECT * FROM TransC1805
UNION ALL
SELECT shop_id,uid,order_id,date,amt1,amt2,amt3,pay_type FROM transD1810; SELECT shop_id,uid,order_id,idate,amt1,amt2,amt3,pay_type FROM TransC1805
UNION ALL
SELECT shop_id,uid,order_id,date,amt1,amt2,amt3,pay_type FROM transD1810;

当要合并的表的字段顺序不一致的时候,手动将所有需要的字段都写出来,并且保证顺序一致。

有时候我们并不需要将表中所有记录和字段都合并,这个时候可以更加灵活使用UNION ALL来完成,比如:将3张表中支付方式为现金(pay_type=1)的交易合并起来,并且保留门店ID、用户ID、交易订单号、交易时间和实际交易额信息。

# 将3张表中支付方式为现金(pay_type=1)的交易合并起来,并且保留门店ID、用户ID、交易订单号、交易时间和实际交易额信息。
SELECT shop_id,uid,order_id,idate,amt3 from TransA1710
where pay_type=1
UNION ALL
SELECT shop_id,uid,order_id,idate,amt3 from TransB1801
where pay_type=1
UNION ALL
SELECT shop_id,uid,order_id,idate,amt3 FROM TransC1805
where pay_type=1;

(2)表连接操作
关于表连接操作就稍微有点复杂了,这里会用到各种 JOIN操作,对于初学者来说,特别容易搞混,下面我们通过两个简单的数据集来学习一下JOIN连接操作:

CREATE TABLE TA(K INT, V VARCHAR(10));
INSERT INTO TA VALUES
(1,"AB"), (2,"A");
SELECT * FROM TA; CREATE TABLE TB(K INT, V VARCHAR(10));
INSERT INTO TB VALUES
(1,"AB"),
(3,"B");
SELECT * FROM TB;

K=1的记录在TA和TB两张表中都有,K=2的记录为TA表中独有,K=3的记录为TB表中独有。

  • 内连接:INNER JOIN
    内连接就是把两张表中共有的数据提取出来。

文氏图:

#内连接
SELECT TA.K AS A_K,TB.K AS B_K,TA.V AS A_V,TB.V AS B_V
FROM TA
INNER JOIN TB
ON TA.K=TB.K;

  • 左连接:LEFT JOIN
    左连接查询会返回左表(TA)中所有记录,不管右表中有没有关联的数据,如果右表中有关联的数据会被一并返回(右表中没有的字段,则以NULL值填充)。
    文氏图:
#左连接
SELECT TA.K AS A_K,TB.K AS B_K,TA.V AS A_V,TB.V AS B_V
FROM TA     #左表
LEFT JOIN TB      #右表
ON TA.K=TB.K;

  • 右连接:
    右连接查询会返回右表(TB)中所有记录,不管左表中有没有关联的数据,如果左表中有关联的数
    据会被一并返回(左表中没有的字段,则以NULL值填充)
    文氏图:
#右连接
SELECT TA.K AS A_K,TB.K AS B_K,TA.V AS A_V,TB.V AS B_V
FROM TA
RIGHT JOIN TB
ON TA.K=TB.K;

  • 全连接:FULL OUTER JOIN
    FULL OUTER JOIN 一般称为全连接或者外连接,实际查询语句中可以写作 FULL OUTER JOIN 或 FULL JOIN 。外连接查询能返回左右表里的所有记录,其中左右表里能关联起来的记录被连接后
    返回。
    文氏图:
# MySQL中不支持FULL OUTER JOIN,直接使用会报错
SELECT TA.K AS A_K,TB.K AS B_K,TA.V AS A_V,TB.V AS B_V
FROM TA
FULL OUTER JOIN TB
ON TA.K=TB.K;
# 可以使用UNION 模拟全连接返回的结果
SELECT TA.K AS A_K,TB.K AS B_K,TA.V AS A_V,TB.V AS B_V  FROM TA
LEFT JOIN TB
ON TA.K=TB.K
UNION
SELECT TA.K AS A_K,TB.K AS B_K,TA.V AS A_V,TB.V AS B_V  FROM TA
RIGHT JOIN TB
ON TA.K=TB.K;


以上就是SQL中常见的四种表连接方式了。此外,还有三种延伸用法,大家感兴趣的话,可以自行研究一下。

(3)综合案例——校园一卡通数据的表连接操作
下面我们通过一个综合案例来学习一下SQL的表连接操作。这里使用的数据是关于校园一卡通的流水记录,数据来源于DC竞赛网,此数据集共包含两部分,分别是学生的图书借阅记录(共包含239947 条数据)和消费记录(共包含 条数据)。

  • 将数据集导入MySQL中
# 创建学生图书借阅记录表
CREATE TABLE stu_borrow
(stu_id VARCHAR(10),
borrow_date DATE,
book_title VARCHAR(500),
book_number VARCHAR(50) );# 导入图书借阅数据
LOAD DATA local INFILE "/Users/zhucan/Desktop/borrow.csv"
INTO TABLE stu_borrow
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n';# 创建学生的一卡通消费表
CREATE TABLE stu_card(
stu_id VARCHAR(10),
custom_class VARCHAR(10),
custom_add VARCHAR(20),
custom_type VARCHAR(20),
custom_date DATETIME,
amt FLOAT,
balance FLOAT );# 导入消费数据
LOAD DATA local INFILE '/Users/zhucan/Desktop/card.txt'
INTO TABLE stu_card111
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n';
  • 简单数据探索
    数据全部导入MySQL之后,需要对数据进行探索,了解数据现状:
# 查询某位同学的借书记录
SELECT * FROM stu_borrow
WHERE stu_id = '9708'
ORDER BY borrow_date;


从返回结果来看,图书借阅记录表存在重复记录,根据实际业务情况,这样的数据是不应该出现的,所以后续进行数据处理的时候,需要做去重处理。

# 查询某位学生的消费记录
SELECT * FROM stu_card
WHERE stu_id = '1040'
ORDER BY custom_date;


同样,对于消费记录来说,也存在重复值,后续数据处理需要做去重操作,并且,消费金额还有复数,这里我们理解为充值行为(因为负的消费金额会导致余额的增加)。

  • 数据处理
    由于两个表中都有重复值,我们首先对数据集进行统计汇总,确保汇总后的结果使得每个学生对应的记录只有一条。对于学生来说,一般以学年为单位进行分析,所以我们选择2014年9月——2015年9月作为统计窗口期。
# 统计2014-9~2015-9学年度每个学生的借书次数以及借阅数量,并将统计结果构成新表
CREATE TABLE borrow_times AS
SELECT stu_id ,COUNT(DISTINCT borrow_date) AS borrow_times ,COUNT(DISTINCT book_title) AS books
FROM stu_borrow
WHERE borrow_date BETWEEN '2014-09-01' AND '2015-08-31'
GROUP BY stu_id;
# 查看统计结果的前5行
SELECT * FROM borrow_times
LIMIT 5;

# 删除stu_card表中重复记录以及消费金额为负的记录,并将清洗结果直接存储到stu_card_distinct表 中
CREATE TABLE stu_card_distinct AS
SELECT DISTINCT * FROM stu_card
WHERE amt>0;
# 如果运行报错:Error Code: 1206. The total number of locks exceeds the lock table size # 说明缓存内存不够(默认为8M),需要设置大一些(比如64M = 64*1024*1024 = 67108864 B)
show variables like "%_buffer_pool_size%";
SET GLOBAL innodb_buffer_pool_size=67108864;
# 统计2014-9~2015-9学年度每个学生的消费总额,最小金额、最大金额和客单价,并将统计结果直接存储到custom表中
CREATE TABLE custom AS
SELECT stu_id ,COUNT(*) AS custom_times ,
SUM(amt) AS custom_amt ,MIN(amt) AS min_amt ,
MAX(amt) AS max_amt ,SUM(amt)/COUNT(*) pct
FROM stu_card_distinct
WHERE custom_date BETWEEN '2014-09-01' AND '2015-08-31'
GROUP BY stu_id;# 查询统计结果的5行信息
SELECT * FROM custom
LIMIT 5;

  • 表连接操作
    将上面处理好的两张表,整合成一张表。具体选择何种连接方式,需要根据实际业务需求。
# 统计结果的整合
SELECT COUNT(*) FROM custom; # 共计5427条
SELECT COUNT(*) FROM borrow_times; # 共计4158条
# 学生消费表custom作为主表,图书借阅表borrow_times作为辅表(左连接)
SELECT t1.*, t2.borrow_times,t2.books FROM custom AS t1
LEFT JOIN borrow_times AS t2
ON t1.stu_id = t2.stu_id;
# 内连接:提取出两张表中共有的学生记录
SELECT t1.*, t2.borrow_times,t2.books FROM custom AS t1
INNER JOIN borrow_times AS t2
ON t1.stu_id = t2.stu_id;


【补充】LOAD DATA的详细用法

详细解释见MySQL用户手册《refman-8.0-en.a4.pdf》P2449-P2459

MySQL——复杂的多表查询——以超市交易数据为例相关推荐

  1. MySQL 笔记5 -- 多表查询

    MySQL 笔记5 – 多表查询 MySQL 系列笔记是笔者学习.实践MySQL数据库的笔记 课程链接: MySQL 数据库基础入门教程 参考文档: MySQL 官方文档 一.表之间关系 1.一对一 ...

  2. mysql的联表查询和去重复数据

    mysql的联表查询和去重复数据 /* SQLyog Ultimate v10.00 Beta1 MySQL - 5.7.17-log : Database - pusmtnew ********** ...

  3. 在MySQL中实现交叉表查询2(动态交叉表)

    在MySQL中实现交叉表查询2(动态交叉表) 交叉表分为静态交叉表和动态交叉表.其中静态交叉表中的列是固定的,因此相对容易实现:而动态交叉表中的列需要动态生成. 一.静态交叉表的实现 参见上一篇文章: ...

  4. 在MySQL中实现交叉表查询1(静态交叉表)

    在MySQL中实现交叉表查询1(静态交叉表) 一.什么是交叉表 交叉表查询是将来源于某个表中的字段进行分组,一组列在交叉表左侧,一组列在交叉表上部,并在交叉表行与列交叉处显示表中某个字段的各种计算值. ...

  5. MySQL基础之多表查询

    目录 1.多表关系 1.1 一对多 1.2 多对多 1.3 一对一 2.多表查询概述 2.1 数据准备 2.2 概述 2.3 分类 3.内连接 4.外连接 5.自连接 5.1 自连接查询 5.2 联合 ...

  6. Mysql 索引 与 多表查询性能优化

    最近做项目需要用到Luence Whoosh,要定时从数据库中索引出数据来供检索,但是在索引中设计多表查询,速度较慢,因为强迫症,想要做性能优化,因此把Mysql的核心又翻出来研究一遍. 关于MySQ ...

  7. MySQL数据库应用 多表查询_mysql数据库-多表查询

    今日任务 完成对MYSQL数据库的多表查询及建表的操作 教学目标 掌握MYSQL中多表的创建及多表的查询 掌握MYSQL中的表关系分析并能正确建表 昨天内容回顾: ​ 数据库的创建 : create ...

  8. 基于SpringDataJpa的mysql动态分页多表查询

    基于SpringDataJpa的mysql动态分页多表查询 由于这篇文章预计篇幅会很长,关于Spring Data JPA的知识就简短的分享,更多的请自行度娘,JPA 封装了很多查询的接口,但今天要讲 ...

  9. 记录:数据库(MySQL)之多表查询

    数据库(MySQL)之多表查询 1.创建表 -- 多表联合查询 -- 创建三个表,并进行插入数据 -- 创建emp表,并插入数据 CREATE TABLE emp ( eno int(5),-- 员工 ...

最新文章

  1. 计算机视觉进展二十年 (1995~2015)
  2. 使用PHP搞定支付宝、微信扫码支付
  3. Windows 10累积更新发布:RS3正式版前最后一更
  4. [问答题] 考SQL语句的题,题太长了,实在不好回忆了。
  5. poj 3278 catch that cow BFS(基础水)
  6. deeplearning中卷积后尺寸的变化
  7. 0113——代理模式
  8. 数百万的 Android 手机预装了危险的恶意软件!
  9. Home vs2013
  10. group by having where order by
  11. 【bzoj2118】 墨墨的等式
  12. Linux储存结构与磁盘分区详解
  13. 树莓派Raspberry Pi 4b+实现摄像头拍照和实时监控
  14. Fibonacci Additions (区间加优化)
  15. Hazel游戏引擎(005)入口点
  16. 对数学规划软件 CPLEX 等读取 MPS 文件的理解
  17. 定义采购申请凭证类型
  18. 设计模式-备忘录模式(Memento)
  19. 1.7 线性无关(第1章 线性代数中的线性方程组)
  20. 【概率论与数理统计】

热门文章

  1. 单曲循环 翻译_有没有那么一首歌是你的单曲循环?
  2. string类有可以调换方向的函数吗_C++中的string类的用法小结
  3. linux 搭建开发stm32 stlink,Ubuntu下搭建stm32+stlink的开发环境
  4. react ui框架_顶级React组件库推荐
  5. java exception e抛异常_抛出的异常在上层catch到,但是e.getMessage()为NULL,为什么会这样?...
  6. 不同表_不同电脑剪视频的速度对比表20200617更新;附素材和方法
  7. 《基于张量网络的机器学习入门》学习笔记4
  8. Second Week: Git与Github的使用
  9. 整合rpc远程调用_远程过程调用(RPC)
  10. 76. Leetcode 295. 数据流的中位数 (堆-技巧一-固定堆)