概述
join操作在進行數據處理時非常常見,而spark支持多種join類型。本文對spark中多種Join類型進行說明,並對不同join的使用場景進行了介紹和舉例說明。
使用join操作的注意事項
- 在兩個數據集比較的列有唯一值,使用默認join(inner join)會有較好的性能,但要注意:兩個數據集中不匹配的key值的數據行將會被丟掉,另外,當比較的列有重複值時,會進行排列組合操作,此時可能會衍生出大量的數據,很可能會產生shuffle。
- 當兩個數據集有重複的key時,執行join操作時可能會讓數據膨脹,此時,最好先對數據集進行distinct或combineByKey操作來減少key值的數量。也可以使用cogroup來處理重複key,避免產生全局排列組合方式來產生數據。在合併時使用智能分區,可以防止join過程中產生二次shuffle。
- 若有的key只存在在兩個數據集中的一箇中,就有可能產生數據丟失,此時使用outer join更加安全。這樣可以保證把數據全都保存在其中的一個數據集中,然後再進行過濾。
- 如果在進行join之前,可以過濾一些key,就儘量過濾掉,這樣可以減少join過程中的數據傳輸量。
- 注意:join是非常耗資源的操作,所以在進行join之前,應該儘量過濾掉不需要的數據。
join類型說明
inner join(默認類型)
默認的join類型,在Spark SQL中的操作是:t1.Id = t2.Id。
- 適用場景
- 兩個數據集比較的列的值唯一(去重),可以有較好的性能
- 注意事項
- 當兩個數據集不匹配時,數據將會被丟掉
- 當比較的列有重複值時,會進行排列組合操作,此時可能會衍生出大量的數據,很可能會產生shuffle。
outer join
- 使用場景
- 需要保留全部數據,而不光是匹配上的數據
- 注意事項
- 使用outer join產生的數據量可能會比較大
- 使用outer join後往往會再進行過濾操作
- 需要根據具體的數據集情況來選擇把數據保存在left還是right的數據集中
join操作及其說明
測試數據集
本文使用以下兩個測試數據集,如下:
- left.csv
id,addr
1,a1
2,a2
3,a3
4,a4
10,
11,
12,
- right.csv
id,name
1,n1
2,n2
3,n3
4,n4
5,n5
6,n6
7,n7
8,n8
9,n9
- 通過pyspark進行加載和測試
left = spark.read.option("header","true").csv("left.csv")
right = spark.read.option("header","true").csv("right.csv")
通過以上代碼得到兩個dataframe。如下:
>>> left.show()
+---+----+
| id|addr|
+---+----+
| 1| a1|
| 2| a2|
| 3| a3|
| 4| a4|
| 10|null|
| 11|null|
| 12|null|
+---+----+
>>> right.show()
+---+----+
| id|name|
+---+----+
| 1| n1|
| 2| n2|
| 3| n3|
| 4| n4|
| 5| n5|
| 6| n6|
| 7| n7|
| 8| n8|
| 9| n9|
+---+----+
通過這兩個dataframe來理解一下的join操作。
inner join(默認)
>>> r = left.join(right, left.id== right.id, "inner")
>>> r.show()
+---+----+---+----+
| id|addr| id|name|
+---+----+---+----+
| 1| a1| 1| n1|
| 2| a2| 2| n2|
| 3| a3| 3| n3|
| 4| a4| 4| n4|
+---+----+---+----+
# 或
>>> r = left.join(right, left.id == right.id)
>>> r.show()
+---+----+---+----+
| id|addr| id|name|
+---+----+---+----+
| 1| a1| 1| n1|
| 2| a2| 2| n2|
| 3| a3| 3| n3|
| 4| a4| 4| n4|
+---+----+---+----+
從以上操作可以看出,默認的==號操作就是inner join。
要點:
- 從以上的結果可以看出,凡是id不相等的列都被過濾掉了,所以要注意inner join會把不匹配的數據丟掉。
left outer join
要點:
- 當id不匹配時,left outer join會保留左邊數據集的全部數據。
- 這樣:左邊數據集的數據不會丟失。
>>> r = left.join(right, left.id== right.id, "leftouter")
>>> r.show()
+---+----+----+----+
| id|addr| id|name|
+---+----+----+----+
| 1| a1| 1| n1|
| 2| a2| 2| n2|
| 3| a3| 3| n3|
| 4| a4| 4| n4|
| 10|null|null|null|
| 11|null|null|null|
| 12|null|null|null|
+---+----+----+----+
用途:
-
當處理兩張全量表時,有時候會找出兩張全量表中的新增id,此時就可以使用left outer join來計算:
select * from A left outer join B on (A.id=B.id) where right.id is null
left semi join和left anti join
要點:
-
left semi join只會保留左邊數據集的字段。而且需要滿足:左邊數據集的id必須在右邊數據集中存在。
-
left anti join也只會保留左邊數據集的字段。而且要滿足條件:左邊數據集的id不能在右邊數據集中。
# left semi join
>>> r = left.join(right, left.id== right.id, "leftsemi")
>>> r.show()
+---+----+
| id|addr|
+---+----+
| 1| a1|
| 2| a2|
| 3| a3|
| 4| a4|
+---+----+
# left anti join,只保留左邊字段,而且id不能在右邊數據集存在
>>> r = left.join(right, left.id== right.id, "leftanti")
>>> r.show()
+---+----+
| id|addr|
+---+----+
| 10|null|
| 11|null|
| 12|null|
+---+----+
right join
right join會保留右邊數據集的全部記錄。包括兩部分:(1)和左邊數據集匹配的記錄,(2)和左邊數據集不匹配的記錄,此時左邊的列會用空填充。
>>> r = left.join(right, left.id== right.id, "right")
>>> r.show()
+----+----+---+----+
| id|addr| id|name|
+----+----+---+----+
| 1| a1| 1| n1|
| 2| a2| 2| n2|
| 3| a3| 3| n3|
| 4| a4| 4| n4|
|null|null| 5| n5|
|null|null| 6| n6|
|null|null| 7| n7|
|null|null| 8| n8|
|null|null| 9| n9|
+----+----+---+----+
從以上結果中可以看出,右邊數據集的數據被全部保留下來了,這樣保證了右邊數據集不丟失。
right semi join和right anti join
這兩個操作和left semi join以及left anti join相似,不同的是:這兩個操作只保留右邊數據集的字段。
(這裏不再累述。)
full outer join
該outer操作是left outer join和right outer join結果的合集。要注意,該操作可能會非常耗時,需要謹慎使用。
>>> r = left.join(right, left.id== right.id, "fullouter")
>>> r.show()
+----+----+----+----+
| id|addr| id|name|
+----+----+----+----+
|null|null| 7| n7|
| 11|null|null|null|
| 3| a3| 3| n3|
|null|null| 8| n8|
|null|null| 5| n5|
|null|null| 6| n6|
|null|null| 9| n9|
| 1| a1| 1| n1|
| 10|null|null|null|
| 4| a4| 4| n4|
| 12|null|null|null|
| 2| a2| 2| n2|
+----+----+----+----+
在join時廣播小數據集
當一個表很小而另一個表很大時,可以使用broadcast方式來進行join。
>>> r = left.join(F.broadcast(right), left.id== right.id, "rightouter")
>>> r.show()
+----+----+---+----+
| id|addr| id|name|
+----+----+---+----+
| 1| a1| 1| n1|
| 2| a2| 2| n2|
| 3| a3| 3| n3|
| 4| a4| 4| n4|
|null|null| 5| n5|
|null|null| 6| n6|
|null|null| 7| n7|
|null|null| 8| n8|
|null|null| 9| n9|
+----+----+---+----+
總結
join操作是比較消耗性能的,在進行join操作時儘可能進行去重,並去掉不需要的數據。另外,可以通過廣播小數據集等方式來加速join的過程。