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
Copyright © 1995–2025 iPentec all rights reserverd.