SQLite | Join 语句

1. Join

我们在上一篇中介绍了 Case 子句
,接下来我们将使用 join ,对表格进行合并。

  • 使用Jupyter Notebook 运行 SQL 语句需安装 ipython-sql

  • %sql 以及 %%sql 为在 Notebook 中运行 SQL 语句,在 SQLite 命令行或 SQLite Stiduo 中不需要 %sql 或 %%sql

载入 SQL 以及连接 SQLite:

%load_ext sql
%sql sqlite:///DataBase/rexon_metals.db
'Connected: @DataBase/rexon_metals.db'

本文将使用 rexon_metals.db 数据库,其中包含了 CUSTOMER、CUSTOMER_ORDER 和 PRODUCT 三张表。

1.1 表联合

Joining 是 SQL 内置的一个子句,但却和其他的子句不同。当我们讨论一个关系数据库时,
里面通常有着几张表,表与表之间通过某一列数据相联系。举个例子,在这样 CUSTOMER_ORDER
表中,有这 CUSTOMER_ID 这一列:

%%sql
select * from customer_order
limit 0,5;
 * sqlite:///DataBase/rexon_metals.db
Done.
ORDER_IDORDER_DATESHIP_DATECUSTOMER_IDPRODUCT_IDORDER_QTYSHIPPED
12015-05-152015-05-1811450false
22015-05-182015-05-2132600false
32015-05-202015-05-2335300false
42015-05-182015-05-2254375false
52015-05-172015-05-2032500false

这一列是我们和 CUSTOMER 表联系起来的键(key),因此你也能猜出,在
CUSTOMER 表中,也有 CUSTOMER_ID 这一列数据:

%%sql
select * from customer
limit 0,5;
 * sqlite:///DataBase/rexon_metals.db
Done.
CUSTOMER_IDNAMEREGIONSTREET_ADDRESSCITYSTATEZIP
1LITE IndustrialSouthwest729 Ravine WayIrvingTX75014
2Rex Tooling IncSouthwest6129 Collie BlvdDallasTX75201
3Re-Barre ConstructionSouthwest9043 Windy DrIrvingTX75032
4Prairie ConstructionSouthwest264 Long RdMooreOK62104
5Marsh Lane Metal WorksSoutheast9143 Marsh LnAvondaleLA79782

我们可以按顺序从这一个表中读取客户的信息,这与 Excel 中的 VLOOKUP 非常相似。

下面是 CUSTOMER_ORDER 表与 CUSTOMER 表之间关系的示意图,我们可以说 CUSTOMER 表是 CUSTOMER_ORDER 表的父表,因为 CUSTOMER_ORDER 表依赖于 CUSOMER 表中的信息,因此它是一个子表。相反的,CUSTOMER 不可能是 CUSTOMER_ORDER 的子表,因为它没有什么信息是依赖于
CUSTOMER_ORDER 表的。

图1 表 CUSTOMER 与表 CUSTOMER_ORDER 关系图

箭头显示了 CUSTOMER 表同过 CUSTOMER_ID 这一列数据与 CUSTOMER_ORDER 表联系。

我们也可以从另一个方面来考虑两个表之间的关系,那就是对于两个表相同的列,子表中的数据通常是重复的,而父表中的数据通常是唯一的。还是以这两张表为例,在下面这张图中你可以看到
一对多的关系:一位 CUSTOMER_ID 为 3 的 Re-Barre Construction 的客户对应着三张订单。

图2 表 CUSTOMER 与表 CUSTOMER_ORDER 关系图2

一对多是最常见的数据关系,因为这符合大多数商业需要,如一个客户对应着多张订单。最不常见的关系就是一对一和多对多的关系。

1.2 内联合

理解了表的关系,我们来考虑下将两张表联合到一起,这样我们可以在同一张表内即看到 CUSTOMER 的数据,又看到 CUSTOMER_ORDER 的数据。

INNER JOIN 让你能够将两张表联合到一起,但如果我们要联合表,就需要定义一个或多个两张表都有的列作为键。如果我们要查询 CUSTOMER_ORDER 并用 CUSTOMER 表来补充客户的信息,我们可以将共同的 CUSTOMER_ID 作为键:

%%sql
SELECT 
order_id,              -- CUSTOMER_ORDER
customer.customer_id,  -- CUSTOMER
order_date,            -- CUSTOMER_ORDER
ship_date,             -- CUSTOMER_ORDER
name,                  -- CUSTOMER
street_address,        -- CUSTOMER
city,                  -- CUSTOMER
state,                 -- CUSTOMER
zip,                   -- CUSTOMER
product_id,            -- CUSTOMER_ORDER
order_qty              -- CUSTOMER_ORDER

FROM customer INNER JOIN customer_order
ON customer.customer_id = customer_order.customer_id
LIMIT 0,5
 * sqlite:///DataBase/rexon_metals.db
Done.
ORDER_IDCUSTOMER_IDORDER_DATESHIP_DATENAMESTREET_ADDRESSCITYSTATEZIPPRODUCT_IDORDER_QTY
112015-05-152015-05-18LITE Industrial729 Ravine WayIrvingTX750141450
232015-05-182015-05-21Re-Barre Construction9043 Windy DrIrvingTX750322600
332015-05-202015-05-23Re-Barre Construction9043 Windy DrIrvingTX750325300
452015-05-182015-05-22Marsh Lane Metal Works9143 Marsh LnAvondaleLA797824375
532015-05-172015-05-20Re-Barre Construction9043 Windy DrIrvingTX750322500

你可能注意到了我们可以同时从 CUSTOMER 和 CUSTOMER_ORDER 两张表中提取列,并将它们合并。

让我们看看刚才的查询是怎么完成的,首先我们从两张表中提取出我们想要的数据:

SELECT 
order_id,              -- CUSTOMER_ORDER
customer.customer_id,  -- CUSTOMER
order_date,            -- CUSTOMER_ORDER
ship_date,             -- CUSTOMER_ORDER
name,                  -- CUSTOMER
street_address,        -- CUSTOMER
city,                  -- CUSTOMER
state,                 -- CUSTOMER
zip,                   -- CUSTOMER
product_id,            -- CUSTOMER_ORDER
order_qty              -- CUSTOMER_ORDER

这样我们可以在每一个订单中看见客户的地址信息。同时也要注意到:由于两张表都具有 ORDER_ID ,所以需要指定使用某一章表的数据。在这个例子中,我们选择了 COSTOMER 表中的 CUSTOMER_ID 数据:

customer.customer_id

最后我们用 select … inner join … 语句临时将两张表合并为一张表。指定了从 CUSTOMER 表中提取数据并加入到 CUSTOMER_ORDER 表中,而它们共同的列就是 CUSTOMER_ID:

FROM customer INNER JOIN customer_order
ON customer.customer_id = customer_order.customer_id

正因为我们是将根据 CUSTOMER_ORDER 上的 CUSTOMER_ID 添加 CUSTOMER 的信息,因此并不是CUSTOMER 上所有的信息都会出现在 CUSTOMER_ORDER 上,以下图为例,当我们合并两张表时,合并的结果中只出现了三个顾客的名字,而由于 CUSTOMER_ORDER 中并没有 Rex Tooling IncPrairie Construction,因此合并表中也就不会出现这两家公司了。

图3 inner join 示意图

1.3 左联合

考虑下我们刚才说到的问题,在内联合中,Rex Tooling IncPrairie Construction 两家公司由于没有订单而没有呈现在最后的合并结果中。但如果我就是想要在合并结果中保留它们呢(尽管没有数据)?

如果你对我们刚才所介绍 inner join 能接受的话,那么 left 或 out join 也就依葫芦画瓢了。你可以将前面代码中的 inner 换成 left ,你会发现左表(CUSTOMER)中所有信息都被保留下来了:

%%sql
SELECT 
order_id,              -- CUSTOMER_ORDER
customer.customer_id,  -- CUSTOMER
order_date,            -- CUSTOMER_ORDER
ship_date,             -- CUSTOMER_ORDER
name,                  -- CUSTOMER
street_address,        -- CUSTOMER
city,                  -- CUSTOMER
state,                 -- CUSTOMER
zip,                   -- CUSTOMER
product_id,            -- CUSTOMER_ORDER
order_qty              -- CUSTOMER_ORDER

FROM customer LEFT JOIN customer_order
ON customer.customer_id = customer_order.customer_id
LIMIT 0,5
 * sqlite:///DataBase/rexon_metals.db
Done.
ORDER_IDCUSTOMER_IDORDER_DATESHIP_DATENAMESTREET_ADDRESSCITYSTATEZIPPRODUCT_IDORDER_QTY
112015-05-152015-05-18LITE Industrial729 Ravine WayIrvingTX750141450
None2NoneNoneRex Tooling Inc6129 Collie BlvdDallasTX75201NoneNone
532015-05-172015-05-20Re-Barre Construction9043 Windy DrIrvingTX750322500
232015-05-182015-05-21Re-Barre Construction9043 Windy DrIrvingTX750322600
332015-05-202015-05-23Re-Barre Construction9043 Windy DrIrvingTX750325300

可以看到虽然 Rex Tooling Inc 并没有订单数据,但会以缺失值的显示出现在结果中,如下图过程所示:

图4 left join 示意图

left join 也经常被用于检查是否有 “孤儿(orphaned)”数据,即子数据无父数据,或相反的父数据无子数据。如用于查找是否有客户没有订单的或订单丢失客户的:

%%sql
SELECT
customer.customer_id,
name AS customer_name

FROM customer LEFT JOIN customer_order
ON customer.customer_id == customer_order.customer_id

WHERE order_id IS null
 * sqlite:///DataBase/rexon_metals.db
Done.
CUSTOMER_IDcustomer_name
2Rex Tooling Inc
4Prairie Construction

1.4 其他联合类型

我们已经介绍了内联合于左联合,SQL 中还包含了其他联合方法,如 right join、out join 等方法。

图5 SQL 表联合方法

right join 与 left join 几乎相同,不过是换了个方向:将右表的所有数据都包含了进来。然而 right join 很少使用,而且你也得避免使用它。要习惯于将保留所有数据的表作为左表!

SQLite 并不支持 right join 和 outer join,但大多数的数据库都支持

1.5 多表联合

由于表与表之间存在着关系,因此关系数据库可以变得异常的复杂。一张子表可能存在着多张父表,同时是其他表的子表(贵圈也挺乱的hhhh),所以它们之间是如何协作的呢?

%%sql
select * from product
limit 0,5
 * sqlite:///DataBase/rexon_metals.db
Done.
PRODUCT_IDDESCRIPTIONPRICE
1Copper7.51
2Aluminum2.58
3Silver15
4Steel12.31
5Bronze4

我们已经探索了 CUSTOMER 和 CUSTOMER_ID 两张表了,但在这个数据库中还有一张 PRODUCT 表我们没用到。在 CUSTOMER_ORDER 和 PRODUCT 两张表中都存在着 PRODUCT_ID 这一列数据,因此我们不仅可以为 CUSTOMER_ORDER 表添加顾客信息,还能添加产品信息。

图6 多表联合

我们可以利用这两个关系将顾客信息和产品信息同时添加到订单中:

%%sql
SELECT 
order_id,                    -- CUSTOMER_ORDER
customer.customer_id,        -- CUSTOMER
name AS customer_name,       -- CUSTOMER
street_address,              -- CUSTOMER
city,                        -- CUSTOMER
state,                       -- CUSTOMER
zip,                         -- CUSTOMER
order_date,                  -- CUSTOMER_ORDER
product.product_id,          -- PRODUCT
description,                 -- PRODUCT
order_qty,                   -- CUSTOMER_ORDER
order_qty * price as revenue -- CUSTOMER_ORDER, PRODUCT

FROM customer LEFT JOIN customer_order
ON customer.customer_id = customer_order.customer_id

LEFT JOIN product
ON customer_order.product_id = product.product_id


LIMIT 0,5
 * sqlite:///DataBase/rexon_metals.db
Done.
ORDER_IDCUSTOMER_IDcustomer_nameSTREET_ADDRESSCITYSTATEZIPORDER_DATEPRODUCT_IDDESCRIPTIONORDER_QTYrevenue
11LITE Industrial729 Ravine WayIrvingTX750142015-05-151Copper4503379.5
None2Rex Tooling Inc6129 Collie BlvdDallasTX75201NoneNoneNoneNoneNone
53Re-Barre Construction9043 Windy DrIrvingTX750322015-05-172Aluminum5001290.0
23Re-Barre Construction9043 Windy DrIrvingTX750322015-05-182Aluminum6001548.0
33Re-Barre Construction9043 Windy DrIrvingTX750322015-05-205Bronze3001200

1.6 分组联合

还是以上面的查询为例,加入我们想要计算每一个顾客的总收入,我们可以在之前查询的基础上使用 group by 子句。为了简便,我们省去了其他的列:

%%sql
select
customer.customer_id,
name as customer_name,
sum(order_qty * price) as total_revenue

from customer inner join customer_order
on customer.customer_id = customer_order.customer_id

inner join product
on customer_order.product_id = product.product_id

group by 1,2
 * sqlite:///DataBase/rexon_metals.db
Done.
CUSTOMER_IDcustomer_nametotal_revenue
1LITE Industrial3379.5
3Re-Barre Construction4038.0
5Marsh Lane Metal Works4616.25

注意到上面的结果中并没有 Rex ToolingPrairie Construction 两家公司的数据,这是因为它们并没有订单。如果你想要保留着部分缺失值,可以使用 left join:

%%sql
select
customer.customer_id,
name as customer_name,
sum(order_qty * price) as total_revenue

from customer left join customer_order
on customer.customer_id = customer_order.customer_id

left join product
on customer_order.product_id = product.product_id

group by 1,2
 * sqlite:///DataBase/rexon_metals.db
Done.
CUSTOMER_IDcustomer_nametotal_revenue
1LITE Industrial3379.5
2Rex Tooling IncNone
3Re-Barre Construction4038.0
4Prairie ConstructionNone
5Marsh Lane Metal Works4616.25

我们对两次联合都使用了 LEFT JOIN 而不是 LEFT JOIN 与 INNER JOIN 混合使用,这是因为 INNER JOIN 会过滤缺失值,而 LEFT JOIN 可以保留缺失值。当我们先使用 LEFT JOIN 并产生缺失值时,如果我们再使用 INNER JOIN,会导致部分缺失值丢失

我们在之前的文章中提过,sum 函数会对非缺失值的数据进行加总,因此如果你想要在查询结果中保留该公司的总收入为 0 的话,可以使用 coalesce 函数将缺失值替换为 0:

%%sql
select
customer.customer_id,
name as customer_name,
coalesce(sum(order_qty * price), 0) as total_revenue

from customer left join customer_order
on customer.customer_id = customer_order.customer_id

left join product
on customer_order.product_id = product.product_id

group by 1,2
 * sqlite:///DataBase/rexon_metals.db
Done.
CUSTOMER_IDcustomer_nametotal_revenue
1LITE Industrial3379.5
2Rex Tooling Inc0
3Re-Barre Construction4038.0
4Prairie Construction0
5Marsh Lane Metal Works4616.25

参考资料

[1] Thomas Nield.Getting Started with SQL[M].US: O’Reilly, 2016: 53-66

相关文章:

SQL | 目录
SQLite | SQLite 与 Pandas 比较篇之一
SQLite | Select 语句
SQLite | Where 子句
SQLite | Group by 与 Order by 子句
SQLite | CASE 子句
SQLite | 数据库设计与 Creat Table 语句
SQLite | Insert、Delete、Updata 与 Drop 语句

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值