LEFT JOIN / INNER JOIN を実行すると同じ内容のレコードが複数含まれる

JOIN を実行すると同じ内容のレコードが複数含まれる現象について紹介します。

例

下記のテーブルを準備します。

Working テーブル
idnamevaluecategorymemo
1Penguin300B南国にすむペンギンです
2Whale420M北極海のクジラです
3Moffu880NULLよくわからない生き物です
4Camel220M砂漠にすむラクダです
5Owl90B関東のフクロウです
6Duck120Bそこらへんのアヒルです


WorkingCategory テーブル
idcategorynameflag
1B鳥類1
2B鳥類2
3M哺乳類1

下記のSQLを実行します。

SELECT Working.name, Working.category, WorkingCategory.name FROM Working
 LEFT JOIN WorkingCategory ON Working.category=WorkingCategory.category;

下記の結果となります。

namecategoryname
PenguinB鳥類
PenguinB鳥類
WhaleM哺乳類
MoffuNULLNULL
CamelM哺乳類
OwlB鳥類
OwlB鳥類
DuckB鳥類
DuckB鳥類

category の値がBのレコードは同じ内容のレコードが2つ含まれています。これは、WorkingCategory テーブルにcategoryの値が"B"で、flagの値が"1"と"2"のレコードの2種類があるため、それぞれが結合された結果2つの同じ値のレコードが出力されます。

INNER JOINを実行した場合もレコードは重複します。

SELECT Working.name, Working.category, WorkingCategory.name FROM Working
 INNER JOIN WorkingCategory ON Working.category=WorkingCategory.category;
namecategoryname
PenguinB鳥類
PenguinB鳥類
WhaleM哺乳類
CamelM哺乳類
OwlB鳥類
OwlB鳥類
DuckB鳥類
DuckB鳥類

対処方法:DISTINCT を利用

出力されるフィールドの値が全く同じ場合は DISTINCT を利用すると回避できます。

下記のSQL文を実行します。

SELECT DISTINCT Working.name, Working.category, WorkingCategory.name FROM Working
 LEFT JOIN WorkingCategory ON Working.category=WorkingCategory.category;


結果は以下になります。

namecategoryname
CamelM哺乳類
DuckB鳥類
MoffuNULLNULL
OwlB鳥類
PenguinB鳥類
WhaleM哺乳類

inner joinの場合は下記です。

SELECT DISTINCT Working.name, Working.category, WorkingCategory.name FROM Working
 INNER JOIN WorkingCategory ON Working.category=WorkingCategory.category;


結果は以下になります。

namecategoryname
CamelM哺乳類
DuckB鳥類
OwlB鳥類
PenguinB鳥類
WhaleM哺乳類


DISTINCT 文の詳細についてはこちらの記事を参照してください。

対処方法:条件を追加

条件で絞り込むことで、結合するレコードが1つになる場合は、結合条件を追加することで回避できます。on節の条件式に and を記述することで複数の条件を結合条件に設定できます。
今回の例の場合WorkingCategory テーブルのflagが1の場合に限り結合すれば、結合するレコードを1つにできます。下記のSQL文実行します。

SELECT Working.name, Working.category, WorkingCategory.name FROM Working
 LEFT JOIN WorkingCategory ON Working.category=WorkingCategory.category AND WorkingCategory.flag=1;

結果は以下になります。

namecategoryname
PenguinB鳥類
WhaleM哺乳類
MoffuNULLNULL
CamelM哺乳類
OwlB鳥類
DuckB鳥類


A.W.S (Another World Story)

LEFT JOIN / INNER JOIN を実行すると同じ内容のレコードが複数含まれる:画像1

新宿の路地裏にある小さな定食屋。木のカウンターに昼の光が斜めに差し込み、味噌汁の湯気が白く揺れている。 プログラマーの彩と DB エンジニアの健司は、揚げたてのアジフライ定食を前にノート PC を覗き込んでいた。

「健司さん、このクエリなんだけど」
彩は箸を止め、画面の一行を指差す。

SELECT * FROM orders o LEFT JOIN order_items i ON o.id = i.order_id

「同じ注文が何度も出てきて、売上が倍々ゲームになっちゃうの。原因がわからなくて」

健司は湯気越しに目を細める。
「orders と order_items は一対多だろ。JOIN した時点で明細の数だけ行が増える。それをそのまま集計したら、そりゃあアジフライが三枚四枚になるさ」

彩は衣のサクッという音を立てながら首をかしげる。
「頭では分かってるつもりだったんだけど、実際に数が合わないと焦るよね」

健司は卓上のしょうゆ差しを持ち上げた。
「定食一皿にしょうゆを三回かければ、見かけ上は定食が三つ並んだように見える。でも定食は一皿だ。数えるときは主キーで数えるか、明細を先にまとめればいい」

彩は眉をひそめ、箸の先で皿をつついた。 「待って、しょうゆを何回振ったかで定食が増えるって、いまいちピンとこないんだけど。ソースを二回かけたらカツが二枚になるわけじゃないでしょ?」

健司は「ああ、たしかに」と笑い、メモ帳にさらさらと書き込む。

SELECT o.id, SUM(i.quantity) AS total_qty
FROM orders o
LEFT JOIN (SELECT order_id, SUM(quantity) AS quantity FROM order_items GROUP BY order_id) i
ON o.id = i.order_id
GROUP BY o.id


彩の顔がぱっと明るくなる。 「これなら定食は一行、しょうゆは合計一列! ダイエット中でも安心だね」

ちょうどそのとき、店員が皿を運んできた。アジフライが三枚も乗っている。
「え、私一枚しか頼んでないんだけど」

店員は伝票を覗き込み、困ったように笑った。
「ご注文は一皿ですが、明細が三行ありまして」

健司が肩をすくめる。
「ほら、LEFT JOIN の罠そのままだな」

彩は苦笑しつつも、衣のカリカリをつまむ。
「DISTINCT で一枚にして返してもらおうかな」

店員は慌てて厨房へ戻り、皿を引っ込めた。だが数分後、戻ってきたのは半切れサイズのアジフライが一枚だけ。
「INNER JOIN に変更していただきました!」

彩は思わず叫ぶ。
「全部なくなるんかい!」

店内がどっと笑いに包まれた。健司は照れたように頭をかき、彩はその小さくなったフライをかじる。

重複レコードもフライも、数え方次第で腹の満ち具合が変わる。ランチタイムの定食屋に、SQL とアジフライの香ばしい余韻が漂っていた。

AuthorPortraitAlt
著者
iPentec.com の代表。ハードウェア、サーバー投資、管理などを担当。
Office 365やデータベースの記事なども担当。
作成日: 2018-08-12