文章目录
- 一、前言
- 二、语法对比
- 数据表
- union
- join
- 1、两表 inner join
- 2、两表 left join
- 3、两表 right join
- 4、两表 outer join
- 5、多表 join
- 三、小结
一、前言
环境:
windows11 64位
Python3.9
MySQL8
pandas1.4.2
本文主要介绍 MySQL 中的union
和join
如何使用pandas实现,同时二者又有什么区别。
注:Python是很灵活的语言,达成同一个目标或有多种途径,我提供的只是其中一种解决方法,大家有其他的方法也欢迎留言讨论。
二、语法对比
数据表
本次使用的数据如下。
使用 Python 构建该数据集的语法如下:
import pandas as pd
import numpy as np
df1 = pd.DataFrame({ 'col1' : list(range(1,7))
,'col2' : ['AA','AA','AA','BB','BB','BB']#list('AABCA')
,'col3' : ['X',np.nan,'Da','Xi','Xa','xa']
,'col4' : [10,5,3,5,2,None]
,'col5' : [90,60,60,80,50,50]
,'col6' : ['Abc','Abc','bbb','Cac','Abc','bbb']
})
df2 = pd.DataFrame({'col2':['AA','BB','CC'],'col7':[1,2,3],'col4':[5,6,7]})
df3 = pd.DataFrame({'col2':['AA','DD','CC'],'col8':[50,70,90]})
注:直接将代码放 jupyter 的 cell 跑即可。后文都直接使用
df1
、df2
、df3
调用对应的数据。
使用 MySQL 构建该数据集的语法如下:
with t1 as(
select 1 as col1, 'AA' as col2, 'X' as col3, 10.0 as col4, 90 as col5, 'Abc' as col6 union all
select 2 as col1, 'AA' as col2, null as col3, 5.0 as col4, 60 as col5, 'Abc' as col6 union all
select 3 as col1, 'AA' as col2, 'Da' as col3, 3.0 as col4, 60 as col5, 'bbb' as col6 union all
select 4 as col1, 'BB' as col2, 'Xi' as col3, 5.0 as col4, 80 as col5, 'Cac' as col6 union all
select 5 as col1, 'BB' as col2, 'Xa' as col3, 2.0 as col4, 50 as col5, 'Abc' as col6 union all
select 6 as col1, 'BB' as col2, 'xa' as col3, null as col4, 50 as col5, 'bbb' as col6
)
,t2 as(
select 'AA' as col2, 1 as col7, 5 as col4 union all
select 'BB' as col2, 2 as col7, 6 as col4 union all
select 'CC' as col2, 3 as col7, 7 as col4
)
,t3 as(
select 'AA' as col2, 50 as col8 union all
select 'DD' as col2, 70 as col8 union all
select 'CC' as col2, 90 as col8
)
select * from t1;
注:直接将代码放 MySQL 代码运行框跑即可。后文跑 SQL 代码时,默认带上数据集(代码的1~18行),仅展示查询语句,如第9行。
对应关系如下:
Python 数据集 | MySQL 数据集 |
---|---|
df1 | t1 |
df2 | t2 |
df3 | t3 |
union
union
的情况比较简单,就分两种:不去重的union all
和去重的union
。
1、不去重 union all
可以使用 Pandas 的pd.concat()
函数实现 MySQL 的union
。
注意向pd.concat()
函数传递一个列表,将所有的数据集作为列表的元素,如果有三个表单进行union
,则给列表3个对应的数据集作为元素即可,有几个加几个。reset_index()
函数是重置索引,而参数drop=True
则是删除旧的索引,如果不删除则是会多加一列index
。如果不需要重置索引,则可以将.reset_index(drop=True)
去掉。
语言 | Python | MySQL |
---|---|---|
代码 | pd.concat([df1.col2,df2.col2]).reset_index(drop=True) | select col2 from t1 union all select col2 from t2; |
结果 |
当union
前后的表单有其他限制的时候,比如下例,则按照上一篇文章《Python和MySQL对比(1):用Pandas 实现MySQL语法效果》中的where
条件进行处理即可。
语言 | Python | MySQL |
---|---|---|
代码 | df1_1 = df1.col2[df1.col2==‘BB’] df2_1 = df2.col2[df2.col2==‘AA’] pd.concat([df1_1, df2_1]).reset_index(drop=True) | select col2 from t1 where col2=‘BB’ union all select col2 from t2 where col2=‘AA’; |
结果 |
2、去重 union
MySQL 使用union
,不加all
,实现对合并的数据表进行去重,在 Pandas 中可以使用df.drop_duplicates()
实现同样的效果。
语言 | Python | MySQL |
---|---|---|
代码 | pd.concat([df1.col2,df2.col2]).drop_duplicates().reset_index(drop=True) | select col2 from t1 union select col2 from t2; |
结果 |
join
join
的内容比较多,分inner join
、left join
、right join
和outer join
。在 Pandas 中,一般使用merge()
实现,另外 Pandas 还有一个生成笛卡尔积的cross join
,不过一般不会用到。用的最多的还是两个基本的inner join
和left join
,left join
的联结方式相对比较好理解一些,right join
一般可以通过left join
实现。
而这些联结方式中,又分单个条件(on
)和多个条件(and
)、两个表的关联和多个表的关联。
下文就分别通过两个表介绍四种联结方法(顺带介绍多条件)和使用inner join
和left join
进行多表关联展开叙述。
1、两表 inner join
单条件,一般只有**on**
**将两个表的一个外键进行关联。**当两个表的键相同的时候,可以使用【代码一】直接使用on
;如果两个表的键不同,则可以使用【代码二】分别用left_on
和right_on
指定对应的键的名称。suffixes
参数是传递两个后缀,当两个表除了关联的外键之外,有同名的字段的时候,则分别给两个表同名的字段加上这里指定的后缀,默认是('_x','_y')
,即左表的同名字段加上_x
后缀,右表的同名字段加上_y
后缀。
【代码一】和【代码二】都是使用pd.merge(df1,df2)
进行操作,该函数的第一个参数是left
即左表,第二个参数是right
即右表;除了使用该方式,也可以使用df1.merge(df2)
,df1
是左表,df2
是右表,其他的参数和上一小段讲的一致。如下【代码三】和【代码四】的示例。
注:由于 MySQL 联结之后,有两个相同的列,第一个列的数据会被第二个列的数据覆盖,所以看到两个col2
和col4
列,且数据一致。而 Pandas 会将键保留一个,其他同名列加上后缀区分。
语言 | Python | MySQL |
---|---|---|
代码 | 【代码一】pd.merge(df1,df2,on=‘col2’,how=‘inner’,suffixes=(‘_left’,‘_right’)) 【代码二】pd.merge(df1,df2,left_on=‘col2’,right_on=‘col2’,how=‘inner’,suffixes=(‘_left’,‘_right’)) 【代码三】 df1.merge(df2,on=‘col2’,how=‘inner’,suffixes=(‘_left’,‘_right’)) 【代码四】 df1.merge(df2,left_on=‘col2’,right_on=‘col2’,how=‘inner’,suffixes=(‘_left’,‘_right’)) | select * from t1 join t2 on t1.col2=t2.col2 ; |
结果 |
多条件-多个键
当多个键联结时,在单个键的基础上修改一下on
或left join
和right join
的参数即可,以列表的形式作为参数值,列表的每个元素是外键的名称,每个外键在列表中的索引位置必须一一对应。
注:单个建,也可以使用列表的形式,传递键,这时列表中只有一个元素。
语言 | Python | MySQL |
---|---|---|
代码 | 【代码一】 pd.merge(df1,df2,left_on=[‘col2’,‘col4’],right_on=[‘col2’,‘col4’],how=‘inner’,suffixes=(‘_left’,‘_right’)) 【代码二】 pd.merge(df1,df2,on=[‘col2’,‘col4’],how=‘inner’,suffixes=(‘_left’,‘_right’)) 【代码三】 df1.merge(df2,left_on=[‘col2’,‘col4’],right_on=[‘col2’,‘col4’],how=‘inner’,suffixes=(‘_left’,‘_right’)) 【代码四】 df1.merge(df2,on=[‘col2’,‘col4’],how=‘inner’,suffixes=(‘_left’,‘_right’)) | select * from t1 join t2 on t1.col2=t2.col2 and t1.col4=t2.col4; |
结果 |
多条件-单表限制条件
以下4个代码返回的结果是一致的。
针对 MySQL 的两个代码,【MySQL1】是join
时同时进行筛选,而【MySQL2】则是先join
完再进行where
。
使用 pandas 实现的时候,可以在merge()
前进行条件筛选,如下【代码一】;也可以在merge()
之后再进行条件筛选,如下【代码二】。
注:用df1.merge(df2)
和pd.merge(df1,df2)
大同小异,针对df1.merge(df2)
不再赘述。
语言 | Python | MySQL |
---|---|---|
代码 | 【代码一】 pd.merge(df1,df2[df2.col2==‘AA’],left_on=[‘col2’],right_on=[‘col2’],how=‘inner’,suffixes=(‘_left’,‘_right’)) 【代码二】 df1_1 = pd.merge(df1,df2,left_on=[‘col2’],right_on=[‘col2’],how=‘inner’,suffixes=(‘_left’,‘_right’)) df1_1[df1_1.col2==‘AA’] | 【MySQL1】 select * from t1 join t2 on t1.col2=t2.col2 and t2.col2=‘AA’; 【MySQL2】 select * from t1 join t2 on t1.col2=t2.col2 where t2.col2=‘AA’; |
结果 |
2、两表 left join
多条件-多键
单条件的比较简单,直接看一个多键的情况。
用 Python 实现left join
的语法和inner join
的语法差不多,只是把how
的参数由inner
改为left
即可。当然,返回的结果是有一定差别的。left join
是以左表为准,将右表能通过外键关联上的字段关联到左表上。
差异:在下面的例子中可以看出一些差异,Python 返回的结果只保留了左表的键,如果需要使用到右表的键,则不能直接使用,需要借助右表其他的列辅助判断。而 MySQL 还是可以通过 t2.col2
和t2.col4
进行调用右表的键。
语言 | Python | MySQL |
---|---|---|
代码 | pd.merge(df1,df2,left_on=[‘col2’,‘col4’],right_on=[‘col2’,‘col4’],how=‘left’,suffixes=(‘_left’,‘_right’)) | select * from t1 left join t2 on t1.col2=t2.col2 and t1.col4=t2.col4; |
结果 | 注:截图中由于有相同列,导致相同列显示数据不准确。 |
多条件-单表限制条件
当有多条件,且是对单独的表单进行限制的时候,和inner join
有一些差异,下图是前面inner join
多条件-单表限制条件的4个代码,结果是一致的。
如果改为left join
,结果会大不同。
如下,【MySQL1】和【MySQL2】结果有很大不同,【MySQL1】是以t1
为主表,and t2.col2='AA'
条件是在关联的时候对t2
进行筛选,筛选完再将t2
关联到t1
上,所以返回的记录都有t1
表的数据,再加上能关联上t1.col2
的t2
表的列数据。而【MySQL2】是以t1
为主表,直接将t2
关联到t1
上,然后再对关联后的数据通过where t2.col2='AA'
进行筛选。
Python 【代码一】效果同【MySQL1】,【代码二】效果同【MySQL2】。
语言 | Python | MySQL |
---|---|---|
代码 | 【代码一】 pd.merge(df1,df2[df2.col2==‘AA’],left_on=[‘col2’],right_on=[‘col2’],how=‘left’,suffixes=(‘_left’,‘_right’)) 【代码二】 df1_1 = pd.merge(df1,df2,left_on=[‘col2’],right_on=[‘col2’],how=‘left’,suffixes=(‘_left’,‘_right’)) df1_1[df1_1.col2==‘AA’] | 【MySQL1】 select * from t1 left join t2 on t1.col2=t2.col2 and t2.col2=‘AA’; 【MySQL2】 select * from t1 left join t2 on t1.col2=t2.col2 where t2.col2=‘AA’; |
结果 |
3、两表 right join
right join
和left join
语法和含义上差不多,如果将左右表位置换一下,就可以通过right join
替换left join
,具体看看下面例子,和left join
的【多条件-多键】的效果(下图)是一致的。
从以下例子可以看出二者的一些差异,Python 返回的结果只保留了右表的键,如果需要使用到左表的键,则不能直接使用,需要借助左表其他的列辅助判断。而 MySQL 还是可以通过 t2.col2
和t2.col4
进行调用左表的键。
语言 | Python | MySQL |
---|---|---|
代码 | pd.merge(df2,df1,left_on=[‘col2’,‘col4’],right_on=[‘col2’,‘col4’],how=‘right’,suffixes=(‘_left’,‘_right’)) | select * from t1 left join t2 on t1.col2=t2.col2 and t1.col4=t2.col4; |
结果 | 注:截图中由于有相同列,导致相同列显示数据不准确。 |
4、两表 outer join
outer join
也叫full join
,在 MySQL 8 中,有一些版本不支持full join
,下表的【MySQL1】使用了full join
的方式进行关联,如果使用不了该语法,也可以使用【MySQL2】或【MySQL3】代替。(注意,由于有多列col2
,第一列会被第二列覆盖,所以为了看出差异,可以对其中一列进行重命名。)
而在 Python 中,没有对outer join
限制,可以一步到位,如下【代码一】,该结果和 MySQL 也有一定的差异,主要是 Python 代码对左右表的col2
列进行了合并,所以看到的col2
列有4个值。另外, Python 的【代码二】和【代码三】也可以实现一样的效果,逻辑和【MySQL2】和【MySQL3】类似,不过由于外键覆盖,使用不到右表的外键,所以【代码三】通过右表的其他字段将空值筛选掉,但是需要保证这个其他字段没有空值,否则会使得原本需要保留的空值也被剔除掉。
语言 | Python | MySQL |
---|---|---|
代码 | 【代码一】 pd.merge(df2,df3,left_on=[‘col2’],right_on=[‘col2’],how=‘outer’,suffixes=(‘_left’,‘_right’)) 【代码二】 df2_1 = pd.merge(df2,df3,left_on=[‘col2’],right_on=[‘col2’],how=‘left’,suffixes=(‘_left’,‘_right’)) df3_1 = pd.merge(df2,df3,left_on=[‘col2’],right_on=[‘col2’],how=‘right’,suffixes=(‘_left’,‘_right’)) pd.concat([df2_1, df3_1]).drop_duplicates().reset_index(drop=True) # 推荐 【代码三】 df2_1 = pd.merge(df2,df3,left_on=[‘col2’],right_on=[‘col2’],how=‘left’,suffixes=(‘_left’,‘_right’)) df3_1 = pd.merge(df2,df3,left_on=[‘col2’],right_on=[‘col2’],how=‘right’,suffixes=(‘_left’,‘_right’)) pd.concat([df2_1, df3_1[df3_1.col4.isna()]]).reset_index(drop=True) # 如果df3_1.col4本身有空值,会影响最终结果 | 【MySQL1】 select * from t2 full join t3 on t3.col2=t2.col2; 【MySQL2】 select * from t2 left join t3 on t3.col2=t2.col2 union all select * from t3 left join t2 on t3.col2=t2.col2 where t2.col2 is null; 【MySQL3】 select * from t2 left join t3 on t3.col2=t2.col2 union all select * from t2 right join t3 on t3.col2=t2.col2 where t2.col2 is null; |
结果 | 注:由于有多个相同列,第一列的数据会被第二列覆盖,可以通过命别名形式避免该问题。 |
其实,本次结果应该如下图所示。MySQL 和 Python 由于一些限制,返回的结果都有一些出入。
该图的 MySQL 代码如下:
select t2.col2 t2_col2,t2.col7,t2.col4,t3.col2 t3_col2,t3.col8
from t2 full join t3 on t3.col2=t2.col2;
-- 或
select t2.col2 t2_col2,t2.col7,t2.col4,t3.col2 t3_col2,t3.col8
from t2 left join t3 on t3.col2=t2.col2
union all
select t2.col2 t2_col2,t2.col7,t2.col4,t3.col2 t3_col2,t3.col8
from t3 left join t2 on t3.col2=t2.col2 where t2.col2 is null;
5、多表 join
多表进行join
的时候,推荐使用df1.merge(df2)
的形式来进行链式join
,如下例子
语言 | Python | MySQL |
---|---|---|
代码 | df1.merge(df2,left_on=‘col2’,right_on=‘col2’,how=‘left’).merge(df3,left_on=‘col2’,right_on=‘col2’,how=‘left’) | select * from t1 left join t2 on t2.col2=t1.col2 left join t3 on t3.col2=t1.col2 |
结果 | 注:由于有多个相同列,第一列的数据会被第二列覆盖,可以通过命别名形式避免该问题。 |
Python 对外键的处理同样存在合并的情况,除了该问题,还需要注意的一点是,当非外键有重复列的时候,Python 会添加后缀,如果后缀在后面的join
中有作为外键使用,则需要注意加上带有外键的字段名称。
语言 | Python | MySQL |
---|---|---|
代码 | df1.merge(df2,left_on=‘col2’,right_on=‘col2’,how=‘left’,suffixes=(‘_x’,‘_y’)).merge(df3,left_on=‘col4_y’,right_on=‘col8’,how=‘left’,suffixes=(‘_left’,‘_right’)) | select * from t1 left join t2 on t2.col2=t1.col2 left join t3 on t3.col8=t2.col4; |
结果 | 注:由于有多个相同列,第一列的数据会被第二列覆盖,可以通过命别名形式避免该问题。 |
三、小结
Python 在多表联结的时候,会对外键进行合并,当使用的是inner join
时,无所谓,但是如果使用的是left join
、right join
或inner join
时,需要注意该差异,避免一些不必要的错误。