spark2實戰-使用Spark SQL的Join

概述

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的過程。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章