大數據場景下,聯表遠比微小型關系型數據庫中使用的頻繁。網上有句話:
傳統數據庫單機模式做Join的場景畢竟有限,也建議盡量減少使用Join。
然而大數據領域就完全不同,Join是標配,OLAP業務根本無法離開表與表之間的關聯,對Join的支持成熟度一定程度上決定了系統的性能,誇張點說,'得Join者得天下'。
-- SparkSQL – 有必要坐下來聊聊Join – 有態度的HBase/Spark/BigData (hbasefly.com)
不同數據庫引擎對JOIN的實現算法一般不同,我們最常用的mysql中的join實現是Nested Loop Join ( MySQL中Join算法實現原理通俗易懂_墨卿風竹的博客-CSDN博客),Spark中支持的要更廣泛。
下面我們創造兩個DF來進行測試。
-
private static List<Customer> getCustomers() {
-
List<Customer> customerList = new ArrayList<>(3);
-
customerList.add(new Customer(100, "張三"));
-
customerList.add(new Customer(101, "張四"));
-
customerList.add(new Customer(102, "張五"));
-
System.out.println("Customer: " + customerList);
-
return customerList;
-
}
-
-
private static List<Payment> getPayments() {
-
Random random = new Random(0);
-
List<Payment> paymentList = new ArrayList<>(6);
-
for (int a = 0; a < 6; a++) {
-
int i = random.nextInt(10000);
-
Payment p = new Payment((long) (a + 1), (long) (100+ a), (double) i);
-
paymentList.add(p);
-
}
-
System.out.println("Payment: " + paymentList);
-
return paymentList;
-
}
Inner Join
內連接是spark默認的連接方式。通過join方法即可使用內連接:
你要這樣用的話,會發現還有一個方法不用傳入連接字段,猜一下輸出什么:
上面這種連接只能指定一個連接字段,如果需要多字段匹配呢?spark提供了另一個方法:
這個方法的第二個參數Java沒法直接提供,需要轉換一下:
left join
DF沒有直接提供leftJoin這樣的方法,只提供了join()和crossJoin()兩個。從上面的文檔截圖可以看到,通過傳第三個參數來指定不同的連接方式。
現在對Java程序員不太友好了,每次join都要先轉一次:可能這也是網上的博客、教程都用scala的原因吧。
right join
和left join類似:
outer join
全外連接是左外和右外的組合,這里不演示了。
cross join
這個上面提到了 ,有對應的方法。它產生的是笛卡爾積,會產生大量結果:
這個方法是2.1之后增加的。之前也是通過join方法實現,但是會被不小心誤用,就增加了一個明確的方法。
Left-Semi-Join
左半連接和左連接比較類似,差別是結果中不包含右表字段,僅包含左表字段。
左連接不是既包含左表字段,又有右表字段,右表中不匹配的字段也顯示但是為null。左半連接是右表不匹配的記錄左表就不展示了,實際更應該叫semi-inner-join。它相當於關系型SQL中的子查詢。
但是由於只返回左表,所以叫左半連接。同時並不提供右半連接操作,因為它就是內連接。
下面是連接方式映射
Left-anti-Join
左反連接是左半連接的取反,並不是右半連接。它展示的是左連接以后,右表是null的那些左表數據,也就是內連接以后左表的補集。相等於關系型數據庫的not in。
Self Join
自連接就是DF跟自己連接,所以需要通過別名來區分兩個DF。
自連接我們再Mysql中用的不少,一般用來查詢層級數據,比如有父子關系的記錄。為了簡單,假設Payment中兩個字段有父子關系,於是這樣查詢:
上面造的數據都不滿足,所以改成這樣:
運行輸出是
如果把第一個參數的開始值改成98,輸出就是
Null 字段匹配
假設在連接過程中(任何連接場景),連接字段出現了null會怎么樣?
假設payement記錄如下
默認情況下,spark會忽略這些記錄,如果不想忽略可以這樣:
-
import static org.apache.spark.sql.functions.col;
-
-
-
Dataset<Row> join = payment.as("c1").join(payment.as("c2"), col("c1.paymentId").eqNullSafe(col("c2.customerId")));
-
join.show();
這里使用了方法eqNullSafe
結果如下
現在連customer也改成null看一下
兩張表內連接結果如下
改成使用方法eqNullSage,結果如下
好像看起來不錯,但你去把結果跟最早的結果(沒比較Null的時候)對比發現,這里customerId出現了兩次,而之前只出現了一次。
這里可以使用drop方法移除列:
-
join.drop("customerId").show();
-
join.drop(payment.col("customerId")).show();
效果可以猜一下。
JoinWith
最后說一下這個方法。從它的簽名可以猜出作用:
把前面內連接的例子改成joinWith方法:
結果中每一行是一個元組,元組的兩個元素分別是兩個表的原始記錄。
最后
已經都來到這了,你不想知道左半連接或左反連接的joinWith結果是啥嗎?