結合!Let's JSON!

前回の勉強内容

ponsuke-tarou.hatenablog.com

勉強のきっかけになった問題

"部品"表から,部品名に'N11'が含まれる部品情報(部品番号,部品名)を検索するSQL文がある。このSQL文は,検索対象の部品情報のほか,対象部品に親部品番号が設定されている場合は親部品情報を返し,設定されていない場合はNULLを返す。aに入れる字句はどれか。ここで,実線の下線は主キーを表す。
 部品(部品番号,部品名,親部品番号)
SQL文〕
SELECT B1.部品番号, B1.部品名,
   B2.部品番号 AS 親部品番号, B2.部品名 AS 親部品名
      FROM 部品 [  a  ]
      ON B1.親部品番号=B2.部品番号
   WHERE B1.部品名 LIKE ‘%N11%’

  1. B1 JOIN 部品 B2
  2. B1 LEFT OUTER JOIN 部品 B2
  3. B1 RIGHT OUTER JOIN 部品 B2
  4. B2 LEFT OUTER JOIN 部品 B1

出典 : 平成30年 春期 データベーススペシャリスト試験 午前Ⅱ 問8

結合にはいろんな方法がある

結合にはたくさん種類があります。「〜結合」がたくさんあるんです、しかも「結合」とは別に「連結」すらあります。
試験はよく日本語で書かれるのですが、日頃SQLでしか書かないのでとっても分かりづらいです。

このページでは「MySQL8.0」を使ってSQLを実際に実行してみました。

自己結合

自己結合は、1つのテーブルを2つあるように見立てて結合することです。自分同士をくっつけます。
「勉強のきっかけになった問題」は、1つの部品テーブルを「B1」「B2」の2つあるように見立てて結合しています。

--       ↓自分と             ↓自分をJOIN
... FROM 部品 B1 LEFT JOIN 部品 B2 ON B1.親部品番号=B2.部品番号 WHERE ...

外部結合 : OUTER JOIN

外部結合は、くっつけるテーブルの「どちらかにしかないレコード」も取得する方法です。

SQLでは「外部結合」という方法で、結合先のテーブルに対応する値を含む行が存在しなくても、結合元の行をすべて表示させることができます。
独習SQL 第2版(長谷川 裕行 黒石 博明)|翔泳社の本

「勉強のきっかけになった問題」の部品テーブルがこんな感じだった場合で考えます。

mysql> select * from 部品;
+--------------+---------------------+-----------------+
| 部品番号     | 部品名              | 親部品番号      |
+--------------+---------------------+-----------------+
| P01          | ペンN11             | NULL            |
| P02          | 修正用品            | NULL            |
| P03          | 留め具              | NULL            |
| P04          | メモ用紙N111        | NULL            |
| S01          | ボールペンN110      | P01             |
| S02          | N11消しゴム         | P02             |
| S03          | クリップM10         | P03             |
| S04          | のりN11             | P03             |
| S05          | N110付箋            | NULL            |
+--------------+---------------------+-----------------+
9 rows in set (0.01 sec)

「勉強のきっかけになった問題」では「対象部品に親部品番号が設定されている場合は親部品情報を返し,設定されていない場合はNULLを返す」ので左に指定される「部品 B1」を全部表示させるために「B1 LEFT OUTER JOIN 部品 B2」が答えになります。

mysql> SELECT B1.部品番号, B1.部品名, B2.部品番号 AS 親部品番号, B2.部品名 AS 親部品名 FROM 部品 B1 LEFT OUTER JOIN 部品 B2 ON B1.親部品番号=B2.部品番号 WHERE B1.部品名 LIKE '%N11%';
+--------------+---------------------+-----------------+--------------+
| 部品番号     | 部品名              | 親部品番号      | 親部品名     |
+--------------+---------------------+-----------------+--------------+
| P01          | ペンN11             | NULL            | NULL         |
| P04          | メモ用紙N111        | NULL            | NULL         |
| S01          | ボールペンN110      | P01             | ペンN11      |
| S02          | N11消しゴム         | P02             | 修正用品     |
| S04          | のりN11             | P03             | 留め具       |
| S05          | N110付箋            | NULL            | NULL         |
+--------------+---------------------+-----------------+--------------+
6 rows in set (0.00 sec)

左外部結合 : LEFT OUTER JOIN / LEFT JOIN

「左」に指定するテーブルのレコードを全部取得する方法です。

"社員取得資格"表に対し,SQL文を実行して結果を得た。SQL文のaに入れる字句はどれか。
社員取得資格

社員コード 資格
S001 FE
S001 AP
S001 DB
S002 FE
S002 SM
S003 FE
S004 AP
S005 NULL

〔結果〕
社員コード  資格1  資格2
------------------------------
S001    FE    AP
S002    FE   NULL
S003    FE    NULL
SQL文〕
SELECT C1.社員コード, C1.資格 AS 資格1, C2.資格 AS 資格2
  FROM 社員取得資格 C1 LEFT OUTER JOIN 社員取得資格 C2 [ a ]

  1. ON C1.社員コード = C2.社員コード AND C1.資格 = 'FE' AND C2.資格 = 'AP' WHERE C1.資格 = 'FE'
  2. ON C1.社員コード = C2.社員コード AND C1.資格 = 'FE' AND C2.資格 = 'AP' WHERE C1.資格 IS NOT NULL
  3. ON C1.社員コード = C2.社員コード AND C1.資格 = 'FE' AND C2.資格 = 'AP' WHERE C2.資格 = 'AP'
  4. ON C1.社員コード = C2.社員コード WHERE C1.資格 = 'FE' AND C2.資格 = 'AP'

出典 : 令和3年 秋期 データベーススペシャリスト試験 午前Ⅱ 問8

-- ①「LEFT OUTER JOIN」なのでC1の全レコードに
mysql> SELECT C1.社員コード, C1.資格 AS 資格1 FROM 社員取得資格 C1;
+-----------------+---------+
| 社員コード      | 資格1   |
+-----------------+---------+
| S001            | FE      |
| S001            | AP      |
| S001            | DB      |
| S002            | FE      |
| S002            | SM      |
| S003            | FE      |
| S004            | AP      |
| S005            | NULL    |
+-----------------+---------+
8 rows in set (0.00 sec)
-- ②C2の「AP」になるレコードを
mysql> SELECT C2.社員コード, C2.資格 AS 資格2 FROM 社員取得資格 C2 WHERE C2.資格 = 'AP';
+-----------------+---------+
| 社員コード      | 資格2   |
+-----------------+---------+
| S001            | AP      |
| S004            | AP      |
+-----------------+---------+
2 rows in set (0.00 sec)
-- ③資格1が「FE」になるレコードだけにくっつけて
mysql> SELECT C1.社員コード, C1.資格 AS 資格1, C2.資格 AS 資格2 FROM 社員取得資格 C1 LEFT OUTER JOIN 社員取得資格 C2 ON C1.社員コード = C2.社員コード AND C1.資格 = 'FE' AND C2.資格 = 'AP';
+-----------------+---------+---------+
| 社員コード      | 資格1   | 資格2   |
+-----------------+---------+---------+
| S001            | FE      | AP      |
| S001            | AP      | NULL    |
| S001            | DB      | NULL    |
| S002            | FE      | NULL    |
| S002            | SM      | NULL    |
| S003            | FE      | NULL    |
| S004            | AP      | NULL    |
| S005            | NULL    | NULL    |
+-----------------+---------+---------+
8 rows in set (0.00 sec)
-- ④資格1が「FE」のレコードだけに絞り込むと〔結果〕になる
mysql> SELECT C1.社員コード, C1.資格 AS 資格1, C2.資格 AS 資格2 FROM 社員取得資格 C1 LEFT OUTER JOIN 社員取得資格 C2 ON C1.社員コード = C2.社 員コード AND C1.資格 = 'FE' AND C2.資格 = 'AP' WHERE C1.資格 = 'FE';
+-----------------+---------+---------+
| 社員コード      | 資格1   | 資格2   |
+-----------------+---------+---------+
| S001            | FE      | AP      |
| S002            | FE      | NULL    |
| S003            | FE      | NULL    |
+-----------------+---------+---------+
3 rows in set (0.01 sec)

右外部結合 : RIGHT OUTER JOIN / RIGHT JOIN

「右」に指定するテーブルのレコードを全部取得する方法です。

完全外部結合 : FULL OUTER JOIN / FULL JOIN

「左右両方」に指定するテーブルのレコードを全部取得する方法です。

表Rと表Sに対し,SQL文を実行して結果を得るとき,aに入れる字句はどれか。ここで,結果のNULLは値が存在しないことを表す。
R

ID 名称1
1 AAA
2 BBB
3 CCC

S

ID 名称2
2 bbb
3 ccc
4 ddd

〔結果〕
 ID  名称1  名称2
------------------------------
 1    AAA  NULL
 2    BBB  bbb
 3    CCC  ccc
 4    NULL  ddd
SQL文〕
SELECT [ a ] (R.ID, S.ID) AS ID, 名称1, 名称2 FROM R FULL OUTER JOIN S ON R.ID = S.ID ORDER BY ID

ア. COALESCE  イ. DISTINCT  ウ. NULLIF  エ. UNIQUE

-- この「FULL JOIN」は、MySQLでは使えません。なので、「LEFT JOIN」「RIGHT JOIN」をくっつけて「FULL JOIN」と同じ結果を取得します。
mysql> SELECT * FROM R LEFT JOIN S ON R.ID = S.ID UNION SELECT * FROM R RIGHT JOIN S ON R.ID = S.ID;
+------+---------+------+---------+
| ID   | 名称1   | ID   | 名称2   |
+------+---------+------+---------+
|    1 | AAA     | NULL | NULL    |
|    2 | BBB     |    2 | bbb     |
|    3 | CCC     |    3 | ccc     |
| NULL | NULL    |    4 | ddd     |
+------+---------+------+---------+
4 rows in set (0.01 sec)
--2つある「ID」のうちNULLじゃない方を返却する関数なのでCOALESCEが正解です。日本語で「融合する」で「コウアレス」と読みます。
mysql> SELECT COALESCE(RID,SID) AS ID,名称1,名称2 FROM (SELECT R.ID AS RID,S.ID AS SID,名称1,名称2 FROM R LEFT JOIN S ON R.ID = S.ID UNION SELECT R.ID AS RID,S.ID AS SID,名称1,名称2 FROM R RIGHT JOIN S ON R.ID = S.ID) AS A;
+------+---------+---------+
| ID   | 名称1   | 名称2   |
+------+---------+---------+
|    1 | AAA     | NULL    |
|    2 | BBB     | bbb     |
|    3 | CCC     | ccc     |
|    4 | NULL    | ddd     |
+------+---------+---------+
4 rows in set (0.00 sec)

内部結合/単純結合 : INNER JOIN / JOIN

内部結合は、くっつけるテーブルの「どっちにもあるレコード」も取得する方法です。

直積結合/交差結合 : CROSS JOIN

直積結合は、くっつけるテーブルの「全部を全組合せ」で取得する方法です。

-- Rテーブルと
mysql> SELECT *  FROM R;
+----+---------+
| ID | 名称1   |
+----+---------+
|  1 | AAA     |
|  2 | BBB     |
|  3 | CCC     |
+----+---------+
3 rows in set (0.01 sec)
-- Sテーブルを
mysql> SELECT * FROM S;
+----+---------+
| ID | 名称2   |
+----+---------+
|  2 | bbb     |
|  3 | ccc     |
|  4 | ddd     |
+----+---------+
3 rows in set (0.00 sec)
-- 直積結合すると全組み合わせが取得できる
mysql> select * from r cross join s;
+----+---------+----+---------+
| ID | 名称1   | ID | 名称2   |
+----+---------+----+---------+
|  3 | CCC     |  2 | bbb     |
|  2 | BBB     |  2 | bbb     |
|  1 | AAA     |  2 | bbb     |
|  3 | CCC     |  3 | ccc     |
|  2 | BBB     |  3 | ccc     |
|  1 | AAA     |  3 | ccc     |
|  3 | CCC     |  4 | ddd     |
|  2 | BBB     |  4 | ddd     |
|  1 | AAA     |  4 | ddd     |
+----+---------+----+---------+
9 rows in set (0.01 sec)

新宿19 東宝

次回の勉強内容

ponsuke-tarou.hatenablog.com