Spark SQL - join の挙動について

Spark SQL の join はいくつか方法があって、それぞれちょっとずつ挙動が違う。

下記のような DataFrame を２つあったとして

```scala
val lowerCase = Seq(
  "a",
  "b",
  "c"
).toDF("lower_case")

val upperCaseMap = Seq(
  ("a", "A"),
  ("b", "B"),
  ("c", "C")
).toDF("lower_case", "lower_case")
```

### `===` を使うパターン

```scala
lowerCase.join(upperCaseMap, lowerCase("lower_case") === upperCaseMap("lower_case"))

+----------+----------+----------+
|lower_case|lower_case|upper_case|
+----------+----------+----------+
|         a|         a|         A|
|         b|         b|         B|
|         c|         c|         C|
+----------+----------+----------+
```

それぞれの DataFrame から結合条件に指定した Column を含めて新しく DataFrame を作成する。
重複が嫌な場合は `drop(Column)` でカラムを削ってしまうのが良さそう。

```scala
lowerCase
  .join(upperCaseMap, lowerCase("lower_case") === upperCaseMap("lower_case"))
  .drop(upperCaseMap("lower_case"))

+----------+----------+
|lower_case|upper_case|
+----------+----------+
|         a|         A|
|         b|         B|
|         c|         C|
+----------+----------+
```

### `Seq(...)` を使うパターン

```scala
lowerCase.join(upperCaseMap, Seq("lower_case"))

+----------+----------+
|lower_case|upper_case|
+----------+----------+
|         a|         A|
|         b|         B|
|         c|         C|
+----------+----------+
```

この場合は Column がマージされる。

### 結合するときに null も考慮したいとき

前段で紹介した2パターンは null 同士の結合を無視する。

null を含む DataFrame。

```scala
val lowerCase = Seq(
  "a",
  "b",
  null
).toDF("lower_case")

val upperCaseMap = Seq(
  ("a", "A"),
  ("b", "B"),
  (null, "Null")
).toDF("lower_case", "upper_case")
```

`===` を使ったとき。`null` の結合は無視される。

```scala
lowerCase.join(upperCaseMap, lowerCase("lower_case") === upperCaseMap("lower_case"))

+----------+----------+----------+
|lower_case|lower_case|upper_case|
+----------+----------+----------+
|         a|         a|         A|
|         b|         b|         B|
+----------+----------+----------+
```

`Seq(...)` を使ったとき、`null` の結合は無視される。

```scala
lowerCase.join(upperCaseMap, Seq("lower_case"))

+----------+----------+
|lower_case|upper_case|
+----------+----------+
|         a|         A|
|         b|         B|
+----------+----------+
```

null の結合をしたい場合は `<=>` か `.eqNullSafe(...)` を使う

`<=>` を使ったとき、`null` の結合ができる。

```scala
lowerCase.join(upperCaseMap, lowerCase("lower_case") <=> upperCaseMap("lower_case"))

+----------+----------+----------+
|lower_case|lower_case|upper_case|
+----------+----------+----------+
|         a|         a|         A|
|         b|         b|         B|
|      null|      null|      Null|
+----------+----------+----------+
```

`.eqNullSafe(...)` を使っても、`null` の結合ができる。

```scala
lowerCase.join(upperCaseMap, lowerCase("lower_case").eqNullSafe(upperCaseMap("lower_case")))

+----------+----------+----------+
|lower_case|lower_case|upper_case|
+----------+----------+----------+
|         a|         a|         A|
|         b|         b|         B|
|      null|      null|      Null|
+----------+----------+----------+
```


### その他

##### 外部結合したいとき

第三引数に形式を指定する。デフォルトは `inner`。

```scala
lowerCase.join(upperCaseMap, lowerCase("lower_case") <=> upperCaseMap("lower_case"), "left")
```

##### 結合条件を複数指定したいとき

`Seq(...)` の場合は必要な分だけ column 名を含めればOK。
他は条件を  `&&` で繋げばOK。

おわり