也許是 SQL 橫向合并的最全解析
、 、 。
今天要把 sql 的橫向合并完整講完,大家請自行根據(jù)掌握程度取用。

一. 橫向不匹配合并
merge 中不使用 by 即為橫向不匹配合并,邏輯是沒有 common key varaible(s),SAS 依據(jù)數(shù)據(jù)集本身固有順序,進行相同順序位次上觀測的橫向合并,直到讀入所有變量所有行。
在 sql 中,我們將 cross join/cartesian product 視為橫向不匹配合并,不同的是,sql 的卡氏積合并并不是基于觀測在數(shù)據(jù)集中的順序,而是生成兩表的卡氏積,即兩表中所有行的所有可能的組合?;诳ㄊ戏e合并的邏輯,sql 對數(shù)據(jù)集進行橫向不匹配合并時,一律不要求數(shù)據(jù)集事先排序。
提醒大家注意??,sql 的橫向合并,無論是否進行匹配,基本邏輯都是卡氏積合并。
之所以要向大家強調這一點,是要和 merge 合并的邏輯作本質的區(qū)分,特別是在一對多、多對多橫向匹配合并時,理解這一點很重要,在文章后面我們會仔細分析。
舉個例子,A 數(shù)據(jù)集有 4 行,B 有 5 行,由于無 common key variables(s),無所謂各數(shù)據(jù)集中是否存在重復,進行橫向不匹配合并后,輸出數(shù)據(jù)集為 4*5= 20 行觀測的卡氏積表。
數(shù)據(jù)集準備:
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 "卡氏積實現(xiàn)形式一";
? ?select *
? ? ? ?from a, b;
quit;
proc sql;
? ?title "卡氏積實現(xiàn)形式二";
? ?Select *
? ? ? ?from a cross join b;
quit;

可以看到,最后是一共 4*5= 20 行的卡氏積表。
另外,你一定注意到,sql 橫向合并時已經(jīng)不存在 merge 那樣的同名變量的處理規(guī)則,即使兩個數(shù)據(jù)集中某些變量名相同,由于處在不同的數(shù)據(jù)集之中,sql 自動將所有同名變量視作非同名變量,保存所有數(shù)據(jù)集的所有變量,即最后的變量數(shù)等于原數(shù)據(jù)集的變量數(shù)之和。
二. 橫向匹配合并
對于橫向匹配合并,sql 分為內連接(inner join)和外連接(outer join),其中,外連接又依據(jù)連接方式可分為 3 種:左連接(left join)、右連接(right join)和全連接(full join)。我們一一來看。
這里,我依然要強調,sql 的橫向合并,無論是否進行匹配,基本邏輯都是卡氏積合并。
1. 一對一橫向匹配合并
1)內連接橫向匹配合并

從 Venn 圖,可以完全理解內連接的含義:指在對數(shù)據(jù)集進行橫向匹配合并時,根據(jù)內連接匹配變量的取值,僅返回兩個數(shù)據(jù)集中內連接匹配變量的值能完全匹配的行。從背后的邏輯來看, 所謂內連接,指 sql 首先進行卡氏積合并,在生成卡氏積表的基礎上,根據(jù) where 或 on 所定的內連接匹配變量,對卡氏積表中的行進行一一判斷,確定其是否滿足 where/on 內連接匹配變量的值完全匹配的條件,刪除不滿足條件的行觀測,最后按 select 選擇的列輸出?;诳ㄊ戏e合并的邏輯,sql 對數(shù)據(jù)集進行內連接橫向匹配合并時,一律不要求數(shù)據(jù)集事先排序。
同樣,對于內連接,有兩種 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 "內連接實現(xiàn)形式一";
? ?select *
? ? ? ?from a, b
? ? ? ? ? ?where a.x= b.x;
quit;
proc sql;
? ?title "內連接實現(xiàn)形式二";
? ?select *
? ? ? ?from a inner join b
? ? ? ? ? ?on a.x= b.x; *注意,由于 inner join 的存在,此時只能用 on 定內連接匹配變量;
quit;

內連接橫向匹配合并的實現(xiàn)結果,當然是沒有問題的。但是我們注意到,由于 sql 自動將所有同名變量視作非同名變量,保存所有數(shù)據(jù)集的所有變量,導致輸出數(shù)據(jù)集中有兩個 value1 變量。
同時,在我們上述內連接結果中,內連接匹配變量 x 也存在相同的兩列。而我們熟悉 merge by 橫向匹配合并的處理結果,即,對于起到數(shù)據(jù)集間橫向匹配合并連接作用的 by 變量,在最終輸出數(shù)據(jù)集中只有一列。在內連接中,這兩列 x 也本應該是完全相同的一列。
想實現(xiàn)和 merge by 橫向匹配合并一樣形式的輸出結果,該如何解決呢?利用 sql 的 coalesce 函數(shù)(返回其參數(shù)中的第一個非缺失值)即可。
proc sql;
? ?title "內連接實現(xiàn)形式一(改進)";
? ?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 "內連接實現(xiàn)形式二(改進)";
? ?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 圖,可以完全理解左連接的含義:指在對數(shù)據(jù)集進行橫向匹配合并時,只根據(jù)左側第一個數(shù)據(jù)集中外連接匹配變量的取值,僅返回左側第一個數(shù)據(jù)集中外連接匹配變量所有取值所對應的所有行。從背后的邏輯來看, 所謂左連接,指 sql 根據(jù)連接方式(左連接)對左側第一個數(shù)據(jù)集中 on 外連接匹配變量的取值做出判斷:若該 on 外連接匹配變量的取值為左側第一個數(shù)據(jù)集所獨有,則將左側第一個數(shù)據(jù)集的該外連接匹配變量取值所對應的行寫入輸出數(shù)據(jù)集,并在輸出數(shù)據(jù)集的該行中將需要橫向匹配合并的第二個數(shù)據(jù)集的全部變量設置為缺失值(這種處理邏輯與一對一 merge by 橫向匹配合并時一致);若該 on 外連接匹配變量的取值非左側第一個數(shù)據(jù)集所獨有,即在兩個數(shù)據(jù)集中能完全匹配,對于兩個數(shù)據(jù)集中該 on 外連接匹配變量取值所對應的行觀測,sql 進行卡氏積合并,生成卡氏積表。最后輸出數(shù)據(jù)集按 select 選擇的列輸出?;诳ㄊ戏e合并的邏輯,sql 對數(shù)據(jù)集進行左連接橫向匹配合并時,一律不要求數(shù)據(jù)集事先排序。
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 圖,可以完全理解右連接的含義:指在對數(shù)據(jù)集進行橫向匹配合并時,只根據(jù)右側第二個數(shù)據(jù)集中外連接匹配變量的取值,僅返回右側第二個數(shù)據(jù)集中外連接匹配變量所有取值所對應的所有行。從背后的邏輯來看, 所謂右連接,指 sql 根據(jù)連接方式(右連接)對右側第二個數(shù)據(jù)集中 on 外連接匹配變量的取值做出判斷:若該 on 外連接匹配變量的取值為右側第二個數(shù)據(jù)集所獨有,則在輸出數(shù)據(jù)集中將需要橫向匹配合并的第一個數(shù)據(jù)集的全部變量設置為缺失值(這種處理邏輯與一對一 merge by 橫向匹配合并時一致),將右側第二個數(shù)據(jù)集的該外連接匹配變量取值所對應的行寫入該行輸出數(shù)據(jù)集;若該 on 外連接匹配變量的取值非右側第二個數(shù)據(jù)集所獨有,即在兩個數(shù)據(jù)集中能完全匹配,對于兩個數(shù)據(jù)集中該 on 外連接匹配變量取值所對應的行觀測,sql 進行卡氏積合并,生成卡氏積表。最后輸出數(shù)據(jù)集按 select 選擇的列輸出。基于卡氏積合并的邏輯,sql 對數(shù)據(jù)集進行右連接橫向匹配合并時,一律不要求數(shù)據(jù)集事先排序。
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 圖,可以完全理解全連接的含義:指在對數(shù)據(jù)集進行橫向匹配合并時,根據(jù)兩個數(shù)據(jù)集中外連接匹配變量的取值,返回兩個數(shù)據(jù)集中外連接匹配變量所有取值所對應的所有行。從背后的邏輯來看, 所謂全連接,指 sql 根據(jù)連接方式(全連接)對兩個數(shù)據(jù)集中 on 外連接匹配變量的取值做出判斷:若某一個 on 外連接匹配變量的取值為某個數(shù)據(jù)集所獨有,則將該數(shù)據(jù)集的該外連接匹配變量取值所對應的行寫入輸出數(shù)據(jù)集,并在輸出數(shù)據(jù)集的該行中將需要橫向匹配合并的另一個數(shù)據(jù)集的全部變量設置為缺失值(這種處理邏輯與一對一 merge by 橫向匹配合并時一致);若某一個 on 外連接匹配變量的取值非某一個數(shù)據(jù)集所獨有,即在兩個數(shù)據(jù)集中能完全匹配,對于兩個數(shù)據(jù)集中該 on 外連接匹配變量取值所對應的行觀測,sql 進行卡氏積合并,生成卡氏積表。最后輸出數(shù)據(jù)集按 select 選擇的列輸出?;诳ㄊ戏e合并的邏輯,sql 對數(shù)據(jù)集進行全連接橫向匹配合并時,一律不要求數(shù)據(jù)集事先排序。
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 的一對一橫向匹配合并,你就能很好地理解它的一對多橫向匹配合并。背后的邏輯是一摸一樣的。
這里,我唯一要提醒的是,大家對下面這個例子的理解情況:
這個例子在
中出現(xiàn)過,即
若是用 sql 來實現(xiàn):
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 無法實現(xiàn)與 merge by 相同的效果。但我這里并不是想強調兩者效果不同這一點,那我想強調什么呢?
如果大家理解了我前面對 merge 的講解,應該知道,對于上面一對三的橫向匹配合并,由于 PDV 的處理 rule,merge by 輸出的是兩個數(shù)據(jù)集中最大的行數(shù),即取 max(1, 3) 為輸出數(shù)據(jù)集的行數(shù);而 sql 并不是這樣,由于 sql 的基本邏輯是做兩個數(shù)據(jù)集的卡氏積合并,即取 1*3 = 3 為最終輸出數(shù)據(jù)集的行數(shù)。雖然這里一對多橫向匹配合并的結果,merge by 和 sql 的輸出都是相同的行數(shù),但是,一旦作多對多橫向匹配合并,二者的基本邏輯上的差異就會自然而然地顯露出來,正所謂,道不同,終將不相為謀。
3. 多對多橫向匹配合并
它背后的邏輯和上面的一對一、一對多橫向匹配合并是一樣的。
關于這種情況的合并,我們后面會把它和 merge by 的多對多橫向匹配合并對比著講,敬請期待。
