Qlik Sense：ロードエディタにおけるJoinについて

# はじめに
Joinを使えば、複数のテーブルを結合できます。
Qlik SenseのロードエディタにおけるJoinの動きと、SQL文のJoinとの対比について整理しました。

# 環境
* Windows11 64bit
* Qlik Sense Desktop 14.78.7

Qlik Senseのロードエディタで検証しましたが、QlikViewのロードスクロプトでも同様の結果になります。

# 構文
Joinの構文です。

出典：[Join ‒ Windows 用 Qlik Sense](https://help.qlik.com/ja-JP/sense/May2022/Subsystems/Hub/Content/Sense_Hub/Scripting/ScriptPrefixes/Join.htm)

```sql
[inner | outer | left | right ]Join [ (tablename ) ]
( loadstatement | selectstatement )
```

引数は次の通りです。

| 引数 | 説明 |
|:--|:--|
| tablename<br>（左テーブル） | 名前が付いたテーブルが、ロード済みのテーブルと比較されます。 |
| loadstatement、または<br>selectstatement<br>（右テーブル） | ロード済みテーブルの LOAD または SELECT ステートメントです。 |

ロード済みの `左テーブル` と `右テーブル` を結合する例です。
同じ名前のフィールドをキーに結合します。

```sql
左テーブル:
LOAD * FROM [左テーブル];

Join(左テーブル)
LOAD * FROM [右テーブル];
```

JOINのあとにテーブルを指定しなければ、直前に読み込んだテーブルと結合します。

```sql
左テーブル:
LOAD * FROM [左テーブル];

Join
LOAD * FROM [右テーブル];
```

# JOINの種類
QlikにおけるJOINには、つぎの種類があります。
* Inner Join
* Left Join
* Right Join
* Outer Join（もしくはJoin）

それぞれの違いについて、実際に実行しながら動きをみていきます。
次の2つのデータをサンプルとして使用します。

* `左テーブル.csv`

	| ID | col_A |
	|:--|:--|
	| A | aaa |
	| B | bbb |
	| C | ccc |

* `右テーブル.csv`

	| ID | col_B |
	|:--|:--|
	| C | ccc |
	| D | ddd |
	| E | eee |

## inner Join
Inner Joinは内部結合ともいいます。
両方のテーブルで結合キーが一致するレコードだけを取得します。

例文は次の通りです。

```sql
左テーブル:
LOAD *
FROM [lib://sample/左テーブル.csv];

Inner Join(左テーブル)
LOAD *
FROM [lib://sample/右テーブル.csv];
```

実行した結果です。

| ID | col_A | col_B |
|:--|:--|:--|
| C | ccc | ccc |

SQL文で書き換えると次のようになります。

```sql
SELECT *
FROM 左テーブル
INNER JOIN 右テーブル
ON 左テーブル.ID = 右テーブル.ID
```



## Left Join
Left Joinは外部結合の一種で、左外部結合ともいいます。
左テーブルの全レコードと、結合キーが一致する右テーブルのレコードを取得します。
結合キーが一致しない場合、Nullを表示します。

例文は次の通りです。

```sql
左テーブル:
LOAD *
FROM [lib://sample/左テーブル.csv];

Left Join(左テーブル)
LOAD *
FROM [lib://sample/右テーブル.csv];
```

実行した結果です。

| ID | col_A | col_B |
|:--|:--|:--|
| A | aaa | - |
| B | bbb | - |
| C | ccc | ccc |

SQL文で書き換えると次のようになります。

```sql
SELECT *
FROM 左テーブル
LEFT JOIN 右テーブル
ON 左テーブル.ID = 右テーブル.ID
```

## Right Join
Right Joinは外部結合の一種で、右外部結合ともいいます。
右テーブルの全レコードと、結合キーが一致する左テーブルのレコードを取得します。
結合キーが一致しない場合、Nullを表示します。

例文は次の通りです。

```sql
左テーブル:
LOAD *
FROM [lib://sample/左テーブル.csv];

Right Join(左テーブル)
LOAD *
FROM [lib://sample/右テーブル.csv];
```

実行した結果です。

| ID | col_A | col_B |
|:--|:--|:--|
| C | ccc | ccc |
| D | - | ddd |
| E | - | eee |

SQL文で書き換えると次のようになります。

```sql
SELECT *
FROM 左テーブル
RIGHT JOIN 右テーブル
ON 左テーブル.ID = 右テーブル.ID
```

## Outer Join
Outer Joinは外部結合の一種です。
両方のテーブルで結合キーが一致するレコードに加え、結合キーが一致しない左テーブルと右テーブルのレコードも表示します。
SQL文におけるFull Join（完全外部結合）と同じ動きになります。
Outerは省略できます。

例文は次の通りです。

```sql
左テーブル:
LOAD *
FROM [lib://sample/左テーブル.csv];

Outer Join(左テーブル)
LOAD *
FROM [lib://sample/右テーブル.csv];
```

実行した結果です。

| ID | col_A | col_B |
|:--|:--|:--|
| A | aaa | - |
| B | bbb | - |
| C | ccc | ccc |
| D | - | ddd |
| E | - | eee |

SQL文で書き換えると次のようになります。

```sql
SELECT *
FROM 左テーブル
FULL JOIN 右テーブル
ON 左テーブル.ID = 右テーブル.ID
```

# 結合キーがないとき
同じ名前のフィールドがない場合の動きをみていきます。
次の2つのデータをサンプルとして使用します。

* `左テーブル_IDなし.csv`

	| col_A |
	|:--|
	| aaa |
	| bbb |
	| ccc |

* `右テーブル_IDなし.csv`

	| col_B |
	|:--|
	| ccc |
	| ddd |
	| eee |

結合キーがない場合、左テーブルの全行に対して右テーブルの全行をつけて表示します。
SQL文におけるCross Join（交差結合）と同じ動きになります。
結合キーがないため、Joinの種類にかかわらずすべて同じ結果となります。

例文は次の通りです。

```sql
左テーブル:
LOAD *
FROM [lib://sample/左テーブル_IDなし.csv];

Join(左テーブル)
LOAD *
FROM [lib://sample/右テーブル_IDなし.csv];
```

実行した結果です。

| col_A | col_B |
|:--|:--|
| aaa | ccc |
| aaa | ddd |
| aaa | eee |
| bbb | ccc |
| bbb | ddd |
| bbb | eee |
| ccc | ccc |
| ccc | ddd |
| ccc | eee |

SQL文で書き換えると次のようになります。

```sql
SELECT *
FROM 左テーブル
CROSS JOIN 右テーブル
```

# おまけ：結合キーが重複している場合について
結合キーが重複している場合の動きについてみていきます。
次の2つのデータをサンプルとして使用します。

* `左テーブル_ID重複あり.csv`
	| ID | col_A |
	|:--|:--|
	| A | aaa |
	| B | bbb |
	| B | www |
	| C | ccc |
	| C | xxx |

* `右テーブル_ID重複あり.csv`
	| ID | col_B |
	|:--|:--|
	| C | ccc |
	| C | yyy |
	| D | ddd |
	| D | zzz |
	| E | eee |

例文は次の通りです。
Left Joinで試してみます。

```sql
左テーブル:
LOAD *
FROM [lib://sample/左テーブル_ID重複あり.csv];

Left Join(左テーブル)
LOAD *
FROM [lib://sample/右テーブル_ID重複あり.csv];
```

実行した結果です。

| ID | col_A | col_B |
|:--|:--|:--|
| A | aaa | - |
| B | bbb | - |
| B | www | - |
| C | ccc | ccc |
| C | ccc | yyy |
| C | xxx | ccc |
| C | xxx | yyy |

SQLと同様、キーが重複している場合は重複して結合します。

# まとめ

| Joinの種類 | 実行結果 | イメージ |
|:--|:--|:-:|
| Inner Join | 両方のテーブルで結合キーが一致するレコードだけを取得する<br>SQL文におけるInner Join（内部結合）と同じ | ![Inner_Join.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/0cbc12d9-6ed3-4a00-7ffa-976b93a74103.png) |
| Left Join | 左テーブルは全件取得する<br>右テーブルは結合したレコードのみ取得する<br>ひもづかないレコードはNullを表示する<br>SQL文におけるLeft Join（左外部結合）と同じ | ![Left_Join.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/3c5b61d0-6337-184b-236c-b0af2dca7407.png) |
| Right Join | 右テーブルは全件取得する<br>左テーブルは結合したレコードのみ取得する<br>ひもづかないレコードはNullを表示する<br>SQL文におけるRight Join（右外部結合）と同じ | ![Right_Join.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/06b4aa4f-1937-aa2c-3ffc-494fb1eed7d7.png) |
| Outer Join<br>もしくはJoin | 両方のテーブルで結合キーが一致するレコードに加え、結合キーが一致しないレコードも表示する<br>ひもづかないレコードはNullを表示する<br>SQL文におけるFull Join（完全外部結合）と同じ<br>Outerは省略できる | ![Outer_Join.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/9a00694d-3906-61de-3d96-6a70e1326509.png) |

| 結合キーについて | 実行結果 | イメージ |
|:--|:--|:-:|
| 結合キーがない場合 | 左テーブルの全行に対して右テーブルの全行をつけて表示する<br>SQL文におけるCross Join（交差結合）と同じ<br>結合キーがないため、Joinの種類にかかわらずすべて同じ結果となる | ![Cross_Join.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/fbcca891-320c-909f-cbdf-495c440589aa.png) |
| 結合キーが重複している場合 | SQLと同様、キーが重複している場合は重複して結合する | ![duplication.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/bcead4c5-22ac-dc03-b5e2-3f56a4ddeca1.png) |