SQLで同じテーブルを結合する方法|自己結合(SELF JOIN)の書き方と実例
SQLの自己結合(SELF JOIN)とは
自己結合は、1つのテーブルに含まれる行どうしの関係を、同じテーブルを2回参照して取り出す方法です。
社員と上司、親カテゴリと子カテゴリ、同じ部署の社員同士など、登場人物や対象が同じ種類のデータに収まっている場面で役立ちます。
通常のJOINとの違い
通常のJOINでは、employeesとdepartmentsのように役割の異なるテーブルを結びます。
自己結合ではemployeesをemployee側とmanager側のように二つの役割へ分け、同じ実体を別のテーブルのように扱います。
テーブルそのものが複製されるわけではなく、SQL文の中で同じテーブル参照を二つ置くと考えると理解しやすくなります。
重要なのは「同じテーブルかどうか」ではなく、ON句でどの行とどの行を対応付けるかです。
たとえば社員行のmanager_idと、上司として参照する社員行のemployee_idを一致させれば、直属上司の名前を取得できます。
自己結合は階層だけに使う技法ではなく、同一グループ内の組み合わせ作成や、値が重複したレコード同士の比較にも利用できます。
結果行が増えるかどうかは、自己結合という名前ではなく、結合条件に一致する行の組み合わせ数で決まります。
SELF JOIN専用の命令はない
SQLにSELF JOINという独立したJOIN種別を記述する必要はありません。
実際に書くのはINNER JOIN、LEFT JOINなど通常のJOINで、FROM句とJOIN句の双方に同じテーブルを指定します。
PostgreSQLの公式文書でも、同じテーブルを自身に結合する例ではテーブル別名を使って二つの参照を区別しています。
SQL Serverの公式情報でも、同一テーブルを二つの役割で扱うself-joinの例に別名が使われています。
そのため、自己結合を覚えるときは新しいJOIN構文を暗記するより、既存のJOINを同一テーブルへ適用する書き方だと整理する方が実務的です。
INNER JOINを使えば対応する相手がある行だけを取得し、LEFT JOINを使えば左側の行を残したまま相手がないケースをNULLとして表せます。
この違いを理解しておくと、最上位の上司や親を持たないカテゴリを結果へ残すかどうかを意図的に選べます。
自己参照の外部キーが定義されているテーブルでは、自己結合で得たい関係がスキーマ上の参照関係と一致するため、まず外部キー列の意味を確認すると結合条件を組み立てやすくなります。
一方で、同じ地域や同じ部署といった属性一致による自己結合は外部キー関係ではないため、何を同一グループと見なすのかを業務条件として明示する必要があります。
自己結合を読むときは、同じテーブル名が二度出てくることに気を取られず、それぞれの別名がどの立場の行を担当しているかを追うと、通常のJOINと同じ考え方で理解できます。
SQLレビューでは、別名を日本語の役割へ置き換えて読み上げ、「社員の上司IDと上司候補の社員IDを結ぶ」のように説明できれば、ON句の向きを確認しやすくなります。
自己結合の用途を一言でまとめるなら、同じ種類のレコードの間にある関係を、1つの結果行へ並べて観察したいときに使う方法です。
自己結合の基本構文
基本形では同じテーブルを二度書き、それぞれに別のテーブル別名を与えてからON句で関係を表します。
最初は構文全体を覚えるより、「左側の役割」「右側の役割」「対応条件」の三つに分けて読むと迷いにくくなります。
エイリアスで役割を分ける
SQL例:SELECT a.column_name, b.column_name FROM table_name a INNER JOIN table_name b ON a.join_key = b.join_key;
aとbは同じtable_nameを指していますが、SQL文の中では別々のテーブル参照として扱われます。
実務ではaやbのような短い名前より、employeeとmanager、childとparentのように役割を表す別名の方が読みやすくなります。
たとえば社員と上司なら、SQL例:FROM employees emp LEFT JOIN employees mgr ON emp.manager_id = mgr.employee_id; と書けます。
empは取得対象の社員、mgrは上司候補の社員という意味になり、同じemployee_name列でもどちらの人物か区別できます。
別名を付けた後は、SELECT句だけでなくON句やWHERE句でも一貫して別名を使うと、列の所属が明確になります。
同じ列名が左右に存在する自己結合では、列名だけを書くと曖昧になりやすいため、修飾名を付ける習慣が特に重要です。
ON句で「どの行とどの行」を結ぶ
自己結合の結果を決める中心はON句です。
社員と上司なら、社員側のmanager_idが上司側のemployee_idに一致するという関係を条件へ書きます。
同じ部署の社員ペアなら、左右のdepartment_idが一致するという条件に加えて、自分自身や逆順の組み合わせをどう扱うかも指定します。
重複データの比較なら、左右のemailが一致することと、左右が別レコードであることを同時に表す必要があります。
ON句の条件が不足すると、意図していない組み合わせまで一致し、行数が急増することがあります。
逆に条件を厳しくしすぎると、本来必要な行まで落ちるため、結合前に「どの関係を1行として返したいか」を言葉で確認すると安全です。
自己結合を書く前に、左側の1行に対して右側が0件、1件、複数件のどれになり得るかを考えると、結果件数を予測しやすくなります。
結合キーにNULLがある場合は通常の等価比較では一致しないため、LEFT JOINで残すのか、WHERE句で除外するのかも設計に含めます。
ASキーワードはDBMSや文脈によってテーブル別名での扱いが異なる場合があるため、移植性を重視するなら対象製品の構文規則を確認し、チーム内で書式を統一すると安全です。
SELECT句に必要な列だけを明示すると、左右に同名列が多い自己結合でも結果の意味が伝わりやすく、不要な列転送を避けられます。
ON句を作る前に、左側の候補キーと右側の参照先キーのデータ型が対応しているかも確認すると、暗黙変換や比較ミスを見つけやすくなります。
WHERE句は結合後の結果を絞る役割を持つため、LEFT JOINで未一致行を残したい場合に右側列の条件をWHEREへ置くと、意図せず行を落とす可能性があります。
可読性を上げるには、FROMとJOINで役割を宣言し、ONで関係を示し、WHEREで対象範囲を絞るという順序を意識して条件を分けると保守しやすくなります。
社員と上司を自己結合する実例
社員と直属上司の取得は、自己結合の仕組みを理解しやすい代表例です。
employeesテーブルにemployee_id、employee_name、manager_idがあり、manager_idが同じテーブルのemployee_idを参照しているケースを考えます。
INNER JOINで社員と上司を取得する
例として、employee_idが1の佐藤はmanager_idがNULL、2の鈴木と3の高橋はmanager_idが1、4の伊藤はmanager_idが2とします。
SQL例:SELECT emp.employee_id, emp.employee_name, mgr.employee_name AS manager_name FROM employees emp INNER JOIN employees mgr ON emp.manager_id = mgr.employee_id ORDER BY emp.employee_id;
このINNER JOINでは、mgr側に一致する上司レコードが存在する社員だけが結果に残ります。
鈴木と高橋は佐藤に、伊藤は鈴木に結び付くため、それぞれの社員名と上司名を同じ結果行で確認できます。
一方、佐藤のmanager_idはNULLなので、等価条件emp.manager_id = mgr.employee_idを満たす相手がありません。
そのため、最上位の管理者も含めて社員一覧を作りたい用途では、INNER JOINだけでは要件を満たせない場合があります。
対応相手が必ず存在するデータだけを対象にしたい処理ではINNER JOINが自然で、欠損や最上位ノードも確認したい一覧では別の選択が必要です。
自己結合だからINNER JOINを選ぶのではなく、結合相手がない左側レコードを残す必要があるかで判断します。
LEFT JOINで上司がいない社員も取得する
上司が登録されていない社員も結果へ残したい場合は、employeesを左側に置いたLEFT JOINが適しています。
SQL例:SELECT emp.employee_id, emp.employee_name, mgr.employee_name AS manager_name FROM employees emp LEFT JOIN employees mgr ON emp.manager_id = mgr.employee_id ORDER BY emp.employee_id;
この書き方ではemp側の社員がすべて残り、上司が見つからない行のmanager_nameはNULLになります。
最上位の管理者を「上司なし」と表示したい場合は、表示用の式としてCOALESCEを組み合わせられます。
SQL例:SELECT emp.employee_name, COALESCE(mgr.employee_name, ‘上司なし’) AS manager_name FROM employees emp LEFT JOIN employees mgr ON emp.manager_id = mgr.employee_id;
ただし、NULLを文字列へ置換すると元データがNULLである事実が見えにくくなるため、後続処理でNULL判定が必要なら生の値を保つ選択もあります。
一覧画面向けの表示と、データ検査向けのSQLでは目的が違うため、同じ自己結合でも出力列の作り方を分けると扱いやすくなります。
上司レコードが削除されてmanager_idだけ残っているような不整合も、LEFT JOINならmanager_nameがNULLになることで検出の手掛かりになります。
このようにLEFT JOINは「上司なし」と「参照先が見つからない」を同じNULLとして返す可能性があるため、業務上区別したい場合は追加条件や整合性制約も確認します。
manager_idへ自己参照の外部キーを設定できる設計では、存在しない上司IDの登録を防ぐ助けになりますが、最上位社員を表すNULLの許可方針は業務要件に合わせて決めます。
複数の上司を持てる組織や期間ごとに上司が変わる履歴管理では、単一のmanager_idだけでは表現できないため、中間テーブルや履歴テーブルを使う設計も検討対象になります。
直属上司だけでなく上司の上司まで固定段数で出したい場合は、mgrをさらに上位の別名へ自己結合できますが、深さが変動するなら再帰CTEの方が自然です。
社員一覧で上司名を表示するだけならLEFT JOINの結果をそのまま利用できますが、未登録上司と最上位社員を区別する必要がある場合はmanager_id自体も同時に出力すると判断しやすくなります。
テストデータには、上司あり、上司なし、複数部下を持つ上司を含めると、INNER JOINとLEFT JOINの違いや結果件数を短いデータで確認できます。
同じ部署に所属する社員の組み合わせを取得する
自己結合は、同じグループに属する行をペアとして取り出す用途でも便利です。
employeesにdepartment_idがある場合、部署ごとの社員ペアを作るときは同じdepartment_idを持つ行同士を結びます。
同一人物どうしの組み合わせを除外する
単純にSQL例:ON emp1.department_id = emp2.department_id; だけを書くと、同じ社員が自分自身とも一致します。
部署に3人いる場合、左右の候補をすべて組み合わせるため、自分自身を含む複数の組み合わせが生成されます。
自分自身だけを除外するなら、SQL例:AND emp1.employee_id <> emp2.employee_id; のように主キーが異なる条件を追加できます。
ただし、この条件だけでは鈴木・高橋と高橋・鈴木のような逆順ペアが両方残ります。
順序を区別する必要がある処理なら両方を残す意味がありますが、単なる組み合わせ一覧では二重計上になりやすい点に注意が必要です。
ペアを作る目的を明確にし、順序に意味があるのか、同じ二人を一組として扱うのかを先に決めることが大切です。
逆順の重複を主キーの大小関係で防ぐ
順序を区別しないペアを一度だけ取得するなら、主キーに大小関係を付ける方法が分かりやすいです。
SQL例:SELECT emp1.employee_name, emp2.employee_name, emp1.department_id FROM employees emp1 INNER JOIN employees emp2 ON emp1.department_id = emp2.department_id AND emp1.employee_id < emp2.employee_id;
employee_idが小さい行だけを左側に置く条件になるため、自分自身は一致せず、逆順のペアも同時に除外できます。
部署内人数がn人なら、順序を区別しないペア数はn×(n-1)÷2になるため、人数が増えるほど結果行も急速に増えます。
たとえば大規模な部署で全ペアを作ると、取得列が少なくても結果件数が非常に多くなる可能性があります。
そのため、実務では対象部署や期間などを先に絞り、必要なペアだけを作る方が安全です。
大小比較に使う列は、重複せず安定した主キーのような列を選ぶと、同じ組み合わせを一意に表しやすくなります。
名前や更新日時のように重複や変更があり得る列を大小比較へ使うと、期待どおりに一組へ絞れないことがあります。
ペア生成では、同じ部署に4人いれば6組、10人いれば45組となるため、元の人数より結果が大きくなることを前提に出力件数を考えます。
大小条件は左右の順序を一意に固定するための手段であり、どちらを左へ置くか自体に業務上の意味がない場合に特に便利です。
もし先輩と後輩のように順序へ意味があるなら、employee_idの大小だけでなく入社日や役職など、業務上の方向を表す条件を使う方が適切な場合があります。
同じ部署という条件に在籍期間も加えるなら、department_id一致だけでは不十分で、比較時点や期間の重なりも条件へ含めないと過去と現在の所属が混在します。
全ペアの抽出は分析用途で便利ですが、アプリケーション画面で表示する場合はページングや対象範囲の限定も考え、不要な組み合わせをDBから返さない設計を優先します。
親カテゴリと子カテゴリを自己結合する実例
カテゴリや組織などの親子関係を同じテーブルで管理している場合も、自己結合で隣接する階層を取得できます。
categoriesにcategory_id、category_name、parent_category_idがある構造なら、子側のparent_category_idと親側のcategory_idを結びます。
1階層の親子関係を取得する
SQL例:SELECT child.category_name AS child_category, parent.category_name AS parent_category FROM categories child LEFT JOIN categories parent ON child.parent_category_id = parent.category_id;
childは表示したいカテゴリ、parentはそのカテゴリの親という役割です。
最上位カテゴリはparent_category_idがNULLになることが多いため、最上位も一覧へ残したいならLEFT JOINが扱いやすくなります。
INNER JOINを使うと親を持つカテゴリだけに絞られるので、階層の途中だけを抽出したい用途ではそれが適する場合もあります。
親子1段分だけを確認したい処理なら、自己結合はSQLが短く、関係も読み取りやすい方法です。
カテゴリ以外にも、コメントの返信元、フォルダの親、部品の上位部品など、同一種類のデータが親IDを持つ設計に応用できます。
2階層以上を固定して取得する
親と祖父母までのように必要な深さが固定されているなら、同じテーブルをさらに別名でJOINできます。
SQL例:SELECT child.category_name, parent.category_name, grandparent.category_name FROM categories child LEFT JOIN categories parent ON child.parent_category_id = parent.category_id LEFT JOIN categories grandparent ON parent.parent_category_id = grandparent.category_id;
この方法ではchild、parent、grandparentという三つの役割を明確にし、階層ごとに結合条件を一段ずつ追加します。
2階層、3階層と上限が決まっている帳票なら、必要な列が横並びになるため扱いやすいことがあります。
ただし階層の深さがデータごとに変わる場合、JOINを何個書けば十分かをSQL作成時点で決められません。
上限を仮定して多数のJOINを書くとSQLが長くなり、要件変更にも弱くなるため、可変長の階層では再帰CTEを検討します。
親IDの参照が循環しているデータでは階層処理が複雑になるため、登録時の整合性や再帰処理の停止条件も別途考える必要があります。
親子テーブルでは、parent_category_idが自分自身のcategory_idを指さないことや、複数ノードで循環しないことを登録時に検証できると、後の階層検索が安定します。
カテゴリ名が重複していてもcategory_idで結べば親子関係は識別できるため、表示名ではなく一意なキーを結合条件に使うことが基本です。
削除された親を参照する孤児レコードがあり得る環境では、LEFT JOINで親名がNULLになる行を抽出し、データ整合性の確認へ利用できます。
階層パスを文字列として表示したいだけでも、固定深度の自己結合を増やし続けるとSQLが複雑になるため、要件が拡張しそうなら早めに再帰処理を検討します。
親子関係の取得と並び順は別問題なので、兄弟カテゴリの表示順が必要ならsort_orderのような列を用意し、JOIN条件とは分けてORDER BYで制御します。
自己結合で重複データを検出する方法
同じメールアドレスや同じ外部IDを持つレコードを「どの行とどの行が重複しているか」まで確認したい場合、自己結合が使えます。
単に重複値と件数を知りたい場合とは目的が違うため、出力したい情報に応じてGROUP BYと使い分けます。
重複しているレコードのペアを取得する
customersにcustomer_idとemailがあるなら、左右のemailが同じという条件で自己結合できます。
SQL例:SELECT c1.customer_id AS customer_id1, c2.customer_id AS customer_id2, c1.email FROM customers c1 INNER JOIN customers c2 ON c1.email = c2.email AND c1.customer_id < c2.customer_id;
customer_idの大小条件を加えることで、同じ行同士と逆順の重複ペアを同時に除外できます。
この結果は、重複している二つの顧客IDを並べて確認したいときや、統合候補の組を作りたいときに向いています。
同じメールアドレスを3件が共有している場合は、3件の中から作れる複数ペアが返るため、1つの重複グループが1行になるわけではありません。
NULLどうしは通常の等価比較では一致しないため、NULLを重複として扱うかどうかは別途ルールを決める必要があります。
メールアドレスの大文字小文字、前後空白、正規化済みかどうかも、単純な等価比較の結果へ影響する可能性があります。
重複判定の前提となるデータ整形をSQL内で行うか、登録時に正規化するかはシステム全体の設計として考えるべきです。
件数だけならGROUP BYとHAVINGが簡潔
重複している値と件数だけを確認したいなら、自己結合より集計の方が目的に直接合います。
SQL例:SELECT email, COUNT(*) AS duplicate_count FROM customers GROUP BY email HAVING COUNT(*) >= 2;
この結果はメールアドレスごとに1行となり、同じ値を持つレコード数を一覧できます。
どのID同士が重複しているかまでは分かりませんが、重複の有無や多さを把握する一次調査には十分なことがあります。
「行同士の関係を見たいなら自己結合」「値ごとの件数を見たいならGROUP BY」と整理すると選びやすくなります。
大量データで重複を調べる場合は、必要な出力形式だけでなく、実行計画や索引の利用状況も確認して処理方法を決めます。
自己結合と集計のどちらが常に速いとは限らないため、対象DBMSとデータ分布で実測することが重要です。
重複候補を実際に統合する前には、メールアドレスだけでなく氏名や登録元など追加属性も並べ、同一人物と判断できる材料を十分に確認する必要があります。
自己結合で候補ペアを作る方法は柔軟ですが、同じ値を共有する件数が多いとペア数が急増するため、事前にGROUP BYで高頻度値を把握してから詳細比較へ進む方法もあります。
重複判定に大文字小文字を無視したい場合や空白を除去したい場合は、DBMSの照合順序や関数の挙動が関係するため、単純なemail一致だけで同一視しない方が安全です。
NULLを重複として扱う要件では、通常の等価演算だけでは候補にならないため、対象DBMSでのNULL比較規則を踏まえて条件を設計します。
検出SQLはデータ修正SQLと分離し、まずSELECTで候補と件数を確認してから更新や削除を別工程にすると、誤った重複判定によるデータ損失を避けやすくなります。
自己結合を使うときの注意点
自己結合は構文自体は単純ですが、左右が同じ列構成なので、普通のJOINより読み違いや条件漏れが起こりやすい面があります。
実行前に役割、結合条件、結果件数、NULLの扱いを順に確認すると、意図しない結果を減らせます。
テーブル別名を必ず付ける
同じテーブルを二度参照するため、左右を区別できる別名が必要です。
SQL例:FROM employees emp JOIN employees mgr ON emp.manager_id = mgr.employee_id;
emp1とemp2でも実行できますが、empとmgrのように意味を持つ別名にすると、後からSQLを読む人が関係を理解しやすくなります。
カテゴリならchildとparent、重複比較ならc1とc2など、役割が分かる名前を選ぶとON句の読み間違いを防げます。
別名が長すぎると記述量が増えるため、短さと意味の分かりやすさのバランスを取ります。
列名にはテーブル別名を付ける
自己結合では左右のテーブル参照が同じ列名を持つため、列名だけではどちらを指すか曖昧になりやすくなります。
SQL例:SELECT emp.employee_name, mgr.employee_name AS manager_name FROM employees emp LEFT JOIN employees mgr ON emp.manager_id = mgr.employee_id;
SELECT句だけでなく、ON句、WHERE句、ORDER BY句でも必要な列へ別名を付けると、条件の向きが見やすくなります。
特にid、name、status、created_atのように多くの箇所で使う列は、左右のどちらかを明示する方が保守しやすくなります。
列別名AS manager_nameのような出力側の名前も付けておくと、アプリケーションから結果を参照するときの意味が分かりやすくなります。
結合による行数の増加に注意する
自己結合では、左側の1行に右側の複数行が一致すると、その数だけ結果行が増えます。
結合条件を付け忘れたり、グループ条件だけで多数の行を結んだりすると、想定より大きな中間結果になることがあります。
同じグループ内の全ペア作成は、グループ人数が増えるほど組み合わせ数が二次的に増えるため、特に件数を見積もる必要があります。
意図しない全組み合わせを避けるため、ON句の条件、自己同一行の除外、逆順重複の除外を実行前に確認してください。
本番データでいきなり試すのではなく、少量のサンプルで期待する組み合わせを手計算してからSQL結果と照合すると原因を切り分けやすくなります。
WHERE句で絞る場合も、LEFT JOIN後に右側列への条件を書くと実質的に未一致行が落ちることがあるため、条件をON句へ置くかWHERE句へ置くかを意識します。
インデックスと実行計画を確認する
自己結合だから必ず遅いわけではありませんが、同じ大きなテーブルを複数の役割で参照するため、結合キーの検索コストは無視できません。
employee_id、manager_id、parent_category_id、emailなど、結合や絞り込みに使う列の索引設計はデータ量と検索条件に合わせて検討します。
主キー側には索引があることが多い一方、外部参照側や検索対象列には別の索引が必要になる場合があります。
索引を増やせば常に良いわけではなく、更新コストや選択性もあるため、実行計画で実際のアクセス方法を確認します。
結果件数が多すぎるSQLは索引だけで解決できないこともあるので、不要なペアを作らない条件設計が先です。
処理が遅い場合は、自己結合という形式だけを疑うのではなく、行数、絞り込み、統計情報、索引、出力列を順に確認すると原因を見つけやすくなります。
本番向けSQLでは、想定行数と実際の取得行数が大きく違った時点で条件を見直せるよう、検証用のCOUNTや限定条件を使って段階的に確認すると安全です。
LEFT JOINの右側列をWHEREで必須条件にすると未一致行が除かれ、結果としてINNER JOINに近い挙動になることがあるため、条件を置く位置はレビュー項目に含めます。
結合キーへ関数を適用すると索引を使いにくくなる場合があるので、性能問題があるときは実行計画を見て、比較前のデータ正規化や関数索引など製品ごとの手段を検討します。
大量の自己結合結果をアプリケーションへ転送するとDB以外の通信やメモリも負荷になるため、必要列だけを選び、不要な全件取得を避けることも重要です。
SQLが正しくても元データの参照関係が壊れていれば期待結果にならないため、クエリだけでなく外部キー制約やデータ品質も合わせて確認してください。
自己結合と再帰CTEの使い分け
自己結合と再帰CTEはどちらも階層データを扱えますが、得意な範囲が異なります。
直属の上司だけを取るのか、組織の末端まで深さを変えながらたどるのかで選択が変わります。
階層数が固定なら自己結合が分かりやすい
直属上司、親カテゴリ、1つ前の工程など、1段だけの関係なら自己結合で十分です。
祖父母までの2段など深さが固定されている場合も、JOINを追加することで横並びの列として取得できます。
必要な深さがSQL作成時に分かっているため、どの別名がどの階層を表すかを明示しやすいのが利点です。
取得したい列がchild、parent、grandparentのように固定された帳票では、再帰処理より結果形をイメージしやすいことがあります。
一方、5段、10段と深さが増えるほどJOIN句が長くなるため、固定深度でも保守性を見て方法を選びます。
階層数が変動するなら再帰CTEを検討する
組織ツリーを最上位から末端までたどる、カテゴリ配下をすべて列挙するなど、深さがデータ次第で変わる処理には再帰CTEが向いています。
PostgreSQLの公式文書でも、再帰WITHは階層や木構造のデータを扱う典型的な用途として説明されています。
再帰CTEでは初期行と再帰部分を組み合わせ、前段で得た行を基に次の階層を繰り返し取得します。
この方式なら深さごとにJOINを手作業で増やす必要はありませんが、循環データや停止条件には注意が必要です。
同じノードを再訪する可能性がある構造では、DBMSが提供する循環検出機能や訪問済み管理の方法を確認します。
再帰CTEの構文や利用できる機能にはDBMSごとの差があるため、実装時は対象製品の公式文書で確認してください。
自己結合と再帰CTEは競合する技法ではなく、隣接1段を取る処理と、深さ不定で連鎖をたどる処理を分担させると理解しやすくなります。
再帰CTEは階層を柔軟にたどれる反面、開始点、再帰条件、終了条件を正しく設計する必要があり、単純な1段取得まで無理に置き換える必要はありません。
固定段数の自己結合は列の位置が明確なので、親と祖父母を別列で必ず返したい帳票では、再帰結果を行方向へ展開するより扱いやすいことがあります。
再帰CTEは結果を行として積み上げる設計が多いため、階層レベルや経路情報を一緒に持たせると、後から並び替えや表示へ利用しやすくなります。
DBMSによって再帰構文や循環検出機能、最適化の特徴が異なるので、汎用的な概念と製品固有の実装を分けて理解することが重要です。
選択に迷ったら、必要な深さがSQL作成時に固定できるかという一点を最初に確認し、固定なら自己結合、変動するなら再帰CTEを候補にすると整理しやすくなります。
よくある質問(Q & A)
ここでは自己結合を書き始めるときに迷いやすい点を、実装判断に直結する形で整理します。
疑問ごとに結論を先に押さえ、必要なら前の実例へ戻って結合条件を見直してください。
SELF JOINというSQL命令を記述する必要がありますか?
必要ありません。
SELF JOINは同じテーブルを自分自身へ結合する使い方の呼び名で、INNER JOINやLEFT JOINなど既存のJOIN構文を使います。
同じテーブルを複数回書き、それぞれに別名を付けて役割を分ける点が特徴です。
検索や解説でSELF JOINという言葉を見ても、専用キーワードを探す必要はありません。
自己結合ではINNER JOINとLEFT JOINのどちらを使いますか?
結合相手がある行だけ必要ならINNER JOIN、相手がない左側の行も残したいならLEFT JOINを選びます。
社員と上司の例では、上司が登録されている社員だけならINNER JOIN、最上位の社員も含めるならLEFT JOINが分かりやすいです。
親カテゴリの例でも、親を持たない最上位カテゴリを残すかどうかで判断できます。
自己結合という理由でJOIN種類を固定せず、未一致行を結果へ残す必要があるかを基準にしてください。
自己結合で同じデータが何度も表示されるのはなぜですか?
ON句の条件に一致する行の組み合わせが複数あるためです。
同じ行同士を除外するだけなら主キーの不一致条件を使えますが、それだけでは左右を入れ替えた逆順ペアが残ることがあります。
順序を区別しない組み合わせなら、SQL例:left_id < right_id; のような大小条件で一方向だけを残せます。
それでも行数が多い場合は、結合キー1件に何件の相手が存在するかを確認すると原因を見つけやすくなります。
自己結合はOracle、MySQL、SQL Server、PostgreSQLで使えますか?
同じテーブルを二つの別名で参照して通常のJOINを行う考え方は、主要なリレーショナルDBMSで利用される一般的な方法です。
ただし、別名の細かな書式、NULL関連関数、再帰CTEの構文、実行計画の見方などには製品差があります。
移植性が必要なSQLでは、自己結合の中心部分を標準的なJOINで書き、製品固有の関数や再帰構文は対象DBMSの公式文書で確認します。
複数製品へ同じSQLを配布する場合は、構文が通ることだけでなく、NULL処理と文字列比較の挙動もテストしてください。
自己結合を使うと処理が遅くなりますか?
自己結合という形式だけで遅いとは決まりません。
性能はテーブル件数、結合条件の選択性、生成される組み合わせ数、索引、統計情報、DBMSの最適化などに左右されます。
同一部署の全ペアのように結果自体が大量になる処理は、索引があっても返す行数が多くなるため、要件側で絞り込みを検討します。
社員と直属上司のように主キーへ1件ずつ結ぶ処理では、全ペア作成とは性質が大きく異なります。
遅さが問題になったら実行計画と実測時間を確認し、推測だけで自己結合を別の構文へ置き換えないことが大切です。
別名の付け方に正解はありませんが、短くても役割が分かる名前を使うと、自己結合を含む長いSQLでも左右の読み違いを減らせます。
自己結合の結果にNULLが見えたときは、LEFT JOINによる未一致なのか、元の列自体がNULLなのかを区別して原因を確認すると判断しやすくなります。
同じ行が多く見える場合はDISTINCTで隠す前に、なぜ複数組が成立しているのかをON句と元データから確認する方が根本原因を見つけやすくなります。
DISTINCTは結果の重複行を除けても、不要な組み合わせを作る処理そのものを防ぐわけではないため、結合条件の代替として安易に使わないことが大切です。
自己結合が複雑になったら、左右それぞれの別名が表す1行を紙やコメントで具体化し、1組だけ手計算するとSQLの意図を再確認できます。
まとめ
同じテーブル内にある行同士の関係を取り出したいとき、自己結合はシンプルで応用範囲の広い方法です。
基本は同じテーブルへ役割別の別名を付け、通常のINNER JOINまたはLEFT JOINで必要な関係をON句に表します。
迷ったときの判断順
まず、左側の1行に対して右側のどの行を対応させたいかを言葉で説明します。
次に、対応相手がない左側行を残す必要があるかを決め、INNER JOINかLEFT JOINを選びます。
組み合わせを作る用途では、自分自身を除外するだけでよいか、逆順の重複も除外するかを判断します。
階層が1段または固定段数なら自己結合、深さがデータによって変わるなら再帰CTEを候補にします。
重複検出では、レコードのペアが必要なら自己結合、値ごとの件数だけならGROUP BYとHAVINGを検討します。
実行前のチェックポイント
テーブル別名が左右の役割を表しているかを確認します。
SELECT、ON、WHERE、ORDER BYで曖昧な列参照が残っていないかを確認します。
ON句に必要な条件がそろい、意図しない全組み合わせを作っていないかを確認します。
NULLを持つ行を残す必要があるなら、JOIN種類とWHERE条件の位置を見直します。
大量データでは結果件数を見積もり、結合キーの索引と実行計画を確認します。
自己結合は「同じテーブルを結ぶ特殊な命令」ではなく、「同じテーブルを別の役割として通常のJOINへ参加させる書き方」と理解すると応用しやすくなります。
自己結合を安定して使うには、構文の暗記よりも、左の役割、右の役割、関係を表すキー、未一致行の扱いという四点を順番に決めることが重要です。
社員と上司のような自己参照、部署内ペアのような属性一致、重複候補のような値比較は同じ自己結合でも目的が違うため、同じテンプレートを無条件に使い回さないようにします。
逆順の重複を除く大小条件は便利ですが、順序に業務上の意味があるケースでは適切な条件が別にあるため、取得したい関係を先に定義してください。
階層処理では1段を自己結合で取る方法を基礎として理解しておくと、固定段数のJOIN追加と可変深度の再帰CTEを状況に応じて選びやすくなります。
最後に、性能や正しさへ不安があるSQLは少量データと実行計画で確認し、結果件数が想定どおりかを確かめてから本番データへ適用するのが安全です。
自己結合の考え方が身に付くと、同じテーブル内にある親子関係や比較対象を、通常のJOINと同じ発想で分解して読めるようになります。
まずは社員と上司のような一対一に近い例で左右の役割を確認し、その後にペア生成や重複検出へ広げると理解しやすくなります。