0
点赞
收藏
分享

微信扫一扫

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

海滨公园 2022-02-10 阅读 98

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

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

通常来说,多表查询主要有两类:一类是纵向的表合并,也就是将结构相同的表上下拼接起来;另一类是横向的表连接,即将多个表中的字段合并到一张大表中。
(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

举报

相关推荐

0 条评论