也许是 SQL 横向合并的最全解析
、、。
今天要把 sql 的横向合并完整讲完,大家请自行根据掌握程度取用。

一. 横向不匹配合并
merge 中不使用 by 即为横向不匹配合并,逻辑是没有 common key varaible(s),SAS 依据数据集本身固有顺序,进行相同顺序位次上观测的横向合并,直到读入所有变量所有行。
在 sql 中,我们将 cross join/cartesian product 视为横向不匹配合并,不同的是,sql 的卡氏积合并并不是基于观测在数据集中的顺序,而是生成两表的卡氏积,即两表中所有行的所有可能的组合。基于卡氏积合并的逻辑,sql 对数据集进行横向不匹配合并时,一律不要求数据集事先排序。
提醒大家注意‼️,sql 的横向合并,无论是否进行匹配,基本逻辑都是卡氏积合并。
之所以要向大家强调这一点,是要和 merge 合并的逻辑作本质的区分,特别是在一对多、多对多横向匹配合并时,理解这一点很重要,在文章后面我们会仔细分析。
举个例子,A 数据集有 4 行,B 有 5 行,由于无 common key variables(s),无所谓各数据集中是否存在重复,进行横向不匹配合并后,输出数据集为 4*5= 20 行观测的卡氏积表。
数据集准备:
data a;
input x value1 $;
datalines;
1 a
2 b
2 y
5 d
;
run;
data b;
input x value2 $;
datalines;
2 x
2 p
2 z
3 c
4 v
;
run;
对于卡氏积合并,有两种 code 形式,二者等价:
proc sql;
title "卡氏积实现形式一";
select *
from a, b;
quit;
proc sql;
title "卡氏积实现形式二";
Select *
from a cross join b;
quit;

可以看到,最后是一共 4*5= 20 行的卡氏积表。
另外,你一定注意到,sql 横向合并时已经不存在 merge 那样的同名变量的处理规则,即使两个数据集中某些变量名相同,由于处在不同的数据集之中,sql 自动将所有同名变量视作非同名变量,保存所有数据集的所有变量,即最后的变量数等于原数据集的变量数之和。
二. 横向匹配合并
对于横向匹配合并,sql 分为内连接(inner join)和外连接(outer join),其中,外连接又依据连接方式可分为 3 种:左连接(left join)、右连接(right join)和全连接(full join)。我们一一来看。
这里,我依然要强调,sql 的横向合并,无论是否进行匹配,基本逻辑都是卡氏积合并。
1. 一对一横向匹配合并
1)内连接横向匹配合并

从 Venn 图,可以完全理解内连接的含义:指在对数据集进行横向匹配合并时,根据内连接匹配变量的取值,仅返回两个数据集中内连接匹配变量的值能完全匹配的行。从背后的逻辑来看, 所谓内连接,指 sql 首先进行卡氏积合并,在生成卡氏积表的基础上,根据 where 或 on 所定的内连接匹配变量,对卡氏积表中的行进行一一判断,确定其是否满足 where/on 内连接匹配变量的值完全匹配的条件,删除不满足条件的行观测,最后按 select 选择的列输出。基于卡氏积合并的逻辑,sql 对数据集进行内连接横向匹配合并时,一律不要求数据集事先排序。
同样,对于内连接,有两种 code 形式,二者等价:
data a;
input x value1 $;
datalines;
1 a
2 b
5 d
;
run;
data b;
input x value1 $ value2 $;
datalines;
2 x a
3 c b
4 v c
;
run;
proc sql;
title "内连接实现形式一";
select *
from a, b
where a.x= b.x;
quit;
proc sql;
title "内连接实现形式二";
select *
from a inner join b
on a.x= b.x; *注意,由于 inner join 的存在,此时只能用 on 定内连接匹配变量;
quit;

内连接横向匹配合并的实现结果,当然是没有问题的。但是我们注意到,由于 sql 自动将所有同名变量视作非同名变量,保存所有数据集的所有变量,导致输出数据集中有两个 value1 变量。
同时,在我们上述内连接结果中,内连接匹配变量 x 也存在相同的两列。而我们熟悉 merge by 横向匹配合并的处理结果,即,对于起到数据集间横向匹配合并连接作用的 by 变量,在最终输出数据集中只有一列。在内连接中,这两列 x 也本应该是完全相同的一列。
想实现和 merge by 横向匹配合并一样形式的输出结果,该如何解决呢?利用 sql 的 coalesce 函数(返回其参数中的第一个非缺失值)即可。
proc sql;
title "内连接实现形式一(改进)";
select coalesce(a.x, b.x) as x, a.value1, b.value1, b.value2
from a, b
where a.x= b.x;
quit;
proc sql;
title "内连接实现形式二(改进)";
select coalesce(a.x, b.x) as x, a.value1, b.value1, b.value2
from a inner join b
on a.x= b.x; *注意,此时只能用 on 定内连接条件;
quit;

2)外连接横向匹配合并
a. 左连接

从 Venn 图,可以完全理解左连接的含义:指在对数据集进行横向匹配合并时,只根据左侧第一个数据集中外连接匹配变量的取值,仅返回左侧第一个数据集中外连接匹配变量所有取值所对应的所有行。从背后的逻辑来看, 所谓左连接,指 sql 根据连接方式(左连接)对左侧第一个数据集中 on 外连接匹配变量的取值做出判断:若该 on 外连接匹配变量的取值为左侧第一个数据集所独有,则将左侧第一个数据集的该外连接匹配变量取值所对应的行写入输出数据集,并在输出数据集的该行中将需要横向匹配合并的第二个数据集的全部变量设置为缺失值(这种处理逻辑与一对一 merge by 横向匹配合并时一致);若该 on 外连接匹配变量的取值非左侧第一个数据集所独有,即在两个数据集中能完全匹配,对于两个数据集中该 on 外连接匹配变量取值所对应的行观测,sql 进行卡氏积合并,生成卡氏积表。最后输出数据集按 select 选择的列输出。基于卡氏积合并的逻辑,sql 对数据集进行左连接横向匹配合并时,一律不要求数据集事先排序。
proc sql;
title "连接条件:left join";
select *
from a left join b
on a.x= b.x;
quit;
proc sql;
title "连接条件:left join,改进";
select coalesce(a.x,b.x) as x, a.value1, b.value1, b.value2
from a left join b
on a.x= b.x;
quit;

b. 右连接

从 Venn 图,可以完全理解右连接的含义:指在对数据集进行横向匹配合并时,只根据右侧第二个数据集中外连接匹配变量的取值,仅返回右侧第二个数据集中外连接匹配变量所有取值所对应的所有行。从背后的逻辑来看, 所谓右连接,指 sql 根据连接方式(右连接)对右侧第二个数据集中 on 外连接匹配变量的取值做出判断:若该 on 外连接匹配变量的取值为右侧第二个数据集所独有,则在输出数据集中将需要横向匹配合并的第一个数据集的全部变量设置为缺失值(这种处理逻辑与一对一 merge by 横向匹配合并时一致),将右侧第二个数据集的该外连接匹配变量取值所对应的行写入该行输出数据集;若该 on 外连接匹配变量的取值非右侧第二个数据集所独有,即在两个数据集中能完全匹配,对于两个数据集中该 on 外连接匹配变量取值所对应的行观测,sql 进行卡氏积合并,生成卡氏积表。最后输出数据集按 select 选择的列输出。基于卡氏积合并的逻辑,sql 对数据集进行右连接横向匹配合并时,一律不要求数据集事先排序。
proc sql;
title "连接条件:right join";
select *
from a right join b
on a.x= b.x;
quit;
proc sql;
title "连接条件:right join,改进";
select coalesce(a.x,b.x) as x, a.value1, b.value1, b.value2
from a right join b
on a.x= b.x;
quit;

c. 全连接

从 Venn 图,可以完全理解全连接的含义:指在对数据集进行横向匹配合并时,根据两个数据集中外连接匹配变量的取值,返回两个数据集中外连接匹配变量所有取值所对应的所有行。从背后的逻辑来看, 所谓全连接,指 sql 根据连接方式(全连接)对两个数据集中 on 外连接匹配变量的取值做出判断:若某一个 on 外连接匹配变量的取值为某个数据集所独有,则将该数据集的该外连接匹配变量取值所对应的行写入输出数据集,并在输出数据集的该行中将需要横向匹配合并的另一个数据集的全部变量设置为缺失值(这种处理逻辑与一对一 merge by 横向匹配合并时一致);若某一个 on 外连接匹配变量的取值非某一个数据集所独有,即在两个数据集中能完全匹配,对于两个数据集中该 on 外连接匹配变量取值所对应的行观测,sql 进行卡氏积合并,生成卡氏积表。最后输出数据集按 select 选择的列输出。基于卡氏积合并的逻辑,sql 对数据集进行全连接横向匹配合并时,一律不要求数据集事先排序。
proc sql;
title "连接条件:full join";
select *
from a full join b
on a.x= b.x;
quit;
proc sql;
title "连接条件:full join,改进";
select coalesce(a.x,b.x) as x, a.value1, b.value1, b.value2
from a full join b
on a.x= b.x;
quit;

2. 一对多横向匹配合并
理解了 sql 的一对一横向匹配合并,你就能很好地理解它的一对多横向匹配合并。背后的逻辑是一摸一样的。
这里,我唯一要提醒的是,大家对下面这个例子的理解情况:
这个例子在 中出现过,即

若是用 sql 来实现:
data a1;
input name $4. class $4.;
cards;
Sue mth
Sue phy
Sue geo
;
run;
data a2;
input name $4. class $4. classno;
cards;
Sue eng 21
;
run;
proc sql;
select coalesce(a1.name, a2.name) as name, a1.class, a2.class, a2.classno
from a1 full join a2
on a1.name= a2.name;
quit;

可见,一对多横向匹配合并时,由于背后的逻辑不同,sql 无法实现与 merge by 相同的效果。但我这里并不是想强调两者效果不同这一点,那我想强调什么呢?
如果大家理解了我前面对 merge 的讲解,应该知道,对于上面一对三的横向匹配合并,由于 PDV 的处理 rule,merge by 输出的是两个数据集中最大的行数,即取 max(1, 3) 为输出数据集的行数;而 sql 并不是这样,由于 sql 的基本逻辑是做两个数据集的卡氏积合并,即取 1*3 = 3 为最终输出数据集的行数。虽然这里一对多横向匹配合并的结果,merge by 和 sql 的输出都是相同的行数,但是,一旦作多对多横向匹配合并,二者的基本逻辑上的差异就会自然而然地显露出来,正所谓,道不同,终将不相为谋。
3. 多对多横向匹配合并
它背后的逻辑和上面的一对一、一对多横向匹配合并是一样的。
关于这种情况的合并,我们后面会把它和 merge by 的多对多横向匹配合并对比着讲,敬请期待。


