SQL Self-Join

6 Kernkonzepte
Die wichtigsten Konzepte für Self-Joins in SQL: INNER JOIN · LEFT JOIN · Hierarchien · Rekursive Queries · Vergleiche · Best Practices Ein Self-Join verbindet eine Tabelle mit sich selbst. Er wird verwendet, um Hierarchien abzubilden, Vergleiche innerhalb einer Tabelle durchzuführen oder Beziehungen zwischen Datensätzen derselben Tabelle zu analysieren.

Self-Join Grundlagen – Tabelle mit sich selbst verbinden

SELECT ... FROM tabelle AS a JOIN tabelle AS b ON ...
SELECT a.name AS "Mitarbeiter", b.name AS "Vorgesetzter" FROM mitarbeiter a LEFT JOIN mitarbeiter b ON a.vorgesetzter_id = b.id;

Self-Join bedeutet, dass eine Tabelle mit sich selbst verbunden wird. Dazu werden Aliase verwendet, um die Tabelle zweimal zu referenzieren – einmal als "links" und einmal als "rechts".

Warum Self-Join?

Anwendungsfall Beschreibung
Hierarchien Mitarbeiter-Vorgesetzte, Kategorien-Baum, Organigramm
Vergleiche Produkte vergleichen, Preisunterschiede innerhalb einer Kategorie
Beziehungen Freundeslisten, Netzwerke, Verweise
Duplikate finden Doppelte Einträge in einer Tabelle identifizieren
Grundlegende Beispiele
-- Mitarbeiter mit ihrem Vorgesetzten
SELECT
m.name AS "Mitarbeiter",
v.name AS "Vorgesetzter"
FROM mitarbeiter m
LEFT JOIN mitarbeiter v
ON m.vorgesetzter_id = v.id;
-- Nur Mitarbeiter mit Vorgesetztem (INNER JOIN)
SELECT m.name, v.name
FROM mitarbeiter m
JOIN mitarbeiter v
ON m.vorgesetzter_id = v.id;
Typische Ausgabe:
Mitarbeiter | Vorgesetzter
Anna Müller | Dr. Schmidt
Ben Weber | Dr. Schmidt
Clara Bauer | Anna Müller
Tipp: Verwenden Sie immer Aliase (z.B. a und b oder m und v) für Self-Joins. Ohne Aliase ist die Abfrage nicht lesbar und nicht ausführbar.

Mitarbeiter-Hierarchie – Vorgesetzte und Untergebene

Employee · Manager · Org Chart
SELECT e.name AS "Employee", m.name AS "Manager" FROM employees e LEFT JOIN employees m ON e.manager_id = m.id ORDER BY m.id NULLS FIRST;

Mitarbeiter-Hierarchien sind der klassische Anwendungsfall für Self-Joins. Jeder Mitarbeiter hat einen Vorgesetzten (Manager), der ebenfalls in der gleichen Tabelle gespeichert ist.

Beispiele
-- Alle Mitarbeiter mit Manager (auch ohne Manager)
SELECT
e.name AS "Mitarbeiter",
m.name AS "Manager"
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.id;
-- Direkte Untergebene eines bestimmten Managers (ID 1)
SELECT e.name
FROM employees e
JOIN employees m
ON e.manager_id = m.id
WHERE m.id = 1;
-- Mitarbeiter, die keinen Vorgesetzten haben (CEO)
SELECT *
FROM employees
WHERE manager_id IS NULL;
-- Alle Mitarbeiter, die einen Manager haben (kein CEO)
SELECT *
FROM employees
WHERE manager_id IS NOT NULL;
Tipp: Für tiefere Hierarchien (z.B. alle Untergebenen eines Managers über mehrere Ebenen) benötigen Sie rekursive CTEs (WITH RECURSIVE), die in PostgreSQL, SQL Server und MySQL 8.0+ verfügbar sind.

Kategorie-Hierarchie – Baumstrukturen abbilden

Category · Parent · Subcategory
SELECT c.name AS "Kategorie", p.name AS "Oberkategorie" FROM kategorien c LEFT JOIN kategorien p ON c.parent_id = p.id;

Kategorie-Hierarchien sind ein weiterer häufiger Anwendungsfall. Jede Kategorie kann eine Oberkategorie (Parent) haben, die ebenfalls in der gleichen Tabelle gespeichert ist.

Beispiele
-- Kategorien mit Oberkategorie
SELECT
c.name AS "Kategorie",
p.name AS "Oberkategorie"
FROM kategorien c
LEFT JOIN kategorien p
ON c.parent_id = p.id
ORDER BY p.id NULLS FIRST;
-- Alle Unterkategorien einer bestimmten Kategorie
SELECT c.name
FROM kategorien c
WHERE c.parent_id = ? -- ID der Oberkategorie;
-- Kategorien ohne Oberkategorie (Top-Level)
SELECT *
FROM kategorien
WHERE parent_id IS NULL;
Typische Ausgabe:
Kategorie | Oberkategorie
Elektronik | (null)
Laptops | Elektronik
Smartphones | Elektronik
Gaming-Laptops | Laptops
Tipp: Bei tiefen Kategorie-Bäumen kann die Performance von Self-Joins leiden. Verwenden Sie Materialized Path (Pfad-Spalte) oder Nested Sets für optimierte Abfragen.

Rekursive Queries – WITH RECURSIVE für tiefe Hierarchien

WITH RECURSIVE cte AS (...)
WITH RECURSIVE org_tree AS ( SELECT id, name, manager_id, 1 AS "level" FROM employees WHERE manager_id IS NULL UNION ALL SELECT e.id, e.name, e.manager_id, t.level + 1 FROM employees e JOIN org_tree t ON e.manager_id = t.id ) SELECT * FROM org_tree;

Rekursive CTEs (WITH RECURSIVE) sind die moderne Lösung für tiefe Hierarchien. Sie durchlaufen den Baum rekursiv und sind in PostgreSQL, SQL Server und MySQL 8.0+ verfügbar.

Beispiele
-- PostgreSQL / SQL Server / MySQL 8.0+
WITH RECURSIVE hierarchy AS (
-- Anchor: Top-Level (CEO)
SELECT id, name, manager_id, 0 AS "level"
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- Rekursiver Teil: Untergebene
SELECT e.id, e.name, e.manager_id, h.level + 1
FROM employees e
JOIN hierarchy h ON e.manager_id = h.id
)
SELECT * FROM hierarchy
ORDER BY level, id;
-- Alle Untergebenen eines bestimmten Managers (z.B. ID 1)
WITH RECURSIVE subordinates AS (
SELECT id, name, manager_id
FROM employees WHERE id = 1
UNION ALL
SELECT e.id, e.name, e.manager_id
FROM employees e
JOIN subordinates s ON e.manager_id = s.id
)
SELECT * FROM subordinates;
Tipp: Rekursive CTEs sind leistungsfähig, aber achten Sie auf Endlosschleifen bei zyklischen Daten. In PostgreSQL können Sie WITH RECURSIVE ... CYCLE verwenden, um Zyklen zu erkennen.

Vergleiche – Daten innerhalb einer Tabelle vergleichen

Preisvergleich · Duplikate · Nachbarn
SELECT a.name, a.preis, b.preis FROM produkte a JOIN produkte b ON a.kategorie = b.kategorie WHERE a.id < b.id;

Self-Joins für Vergleiche ermöglichen es, Datensätze innerhalb einer Tabelle zu vergleichen – z.B. Preisunterschiede innerhalb einer Kategorie, Duplikate oder benachbarte Datensätze.

Beispiele
-- Preisvergleich innerhalb gleicher Kategorie
SELECT
a.name AS "Produkt A", a.preis AS "Preis A",
b.name AS "Produkt B", b.preis AS "Preis B"
FROM produkte a
JOIN produkte b
ON a.kategorie = b.kategorie
WHERE a.id < b.id -- Duplikate vermeiden
ORDER BY a.kategorie, a.preis;
-- Duplikate finden (gleicher Name, gleiche Kategorie)
SELECT a.id, a.name, a.kategorie
FROM produkte a
JOIN produkte b
ON a.name = b.name
AND a.kategorie = b.kategorie
WHERE a.id < b.id;
-- Produkte, die teurer sind als der Durchschnitt ihrer Kategorie
SELECT p.name, p.preis, p.kategorie
FROM produkte p
JOIN (
SELECT kategorie, AVG(preis) AS "avg_preis"
FROM produkte
GROUP BY kategorie
) stats ON p.kategorie = stats.kategorie
WHERE p.preis > stats.avg_preis;
Tipp: Bei Vergleichen mit Self-Joins verwenden Sie die Bedingung a.id < b.id oder a.id != b.id, um zu verhindern, dass jedes Paar doppelt auftaucht.

Praxis & Best Practices – Tipps für Self-Joins

Aliase · Index · NULL · Performance
-- Best Practice Beispiel SELECT e.name AS "Employee", m.name AS "Manager" FROM employees e LEFT JOIN employees m ON e.manager_id = m.id;

Best Practices für Self-Joins helfen, performante, lesbare und wartbare Abfragen zu schreiben. Hier sind die wichtigsten Tipps.

Best Practices Übersicht

Praxis Beschreibung
Aliase verwenden Immer aussagekräftige Aliase (z.B. e/m, a/b, parent/child)
LEFT JOIN für NULL Verwenden Sie LEFT JOIN, wenn Datensätze ohne Partner (z.B. CEO) enthalten sein sollen
Indizes anlegen Indizes auf den Fremdschlüssel-Spalten (z.B. manager_id) beschleunigen Self-Joins
WHERE-Klausel prüfen Bei INNER JOIN werden Datensätze ohne Partner ausgeschlossen
Rekursive CTEs nutzen Für tiefe Hierarchien (mehrere Ebenen) sind rekursive CTEs die beste Lösung
Zyklen vermeiden Achten Sie auf zyklische Beziehungen (z.B. A → B → A) – diese führen zu Endlosschleifen
Beispiele
-- Richtige Verwendung von LEFT JOIN (zeigt auch Mitarbeiter ohne Manager)
SELECT e.name, m.name
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.id;
-- Index für self-join Spalten
-- CREATE INDEX idx_employees_manager_id ON employees (manager_id);
-- Self-Join mit zusätzlicher Filterung
SELECT e.name, m.name
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.id
WHERE e.aktiv = true;
-- Self-Join mit Aggregation (Anzahl der Untergebenen)
SELECT m.name, COUNT(e.id) AS "untergebene"
FROM employees m
LEFT JOIN employees e
ON e.manager_id = m.id
GROUP BY m.id, m.name
ORDER BY "untergebene" DESC;
Typische Ausgabe (untergebene zählen):
Manager | untergebene
Dr. Schmidt | 5
Anna Müller | 2
Ben Weber | 0
Tipp: Bei großen Tabellen können Self-Joins langsam werden. Verwenden Sie EXPLAIN (oder EXPLAIN ANALYZE) um den Ausführungsplan zu analysieren und fehlende Indizes zu identifizieren.

Self-Join im Überblick

Alias Tabelle zweimal referenzieren
a, b · e, m · parent, child
INNER JOIN Nur Datensätze mit Partner
z.B. nur Mitarbeiter mit Manager
LEFT JOIN Alle Datensätze (auch ohne Partner)
z.B. CEO ohne Manager
Hierarchie Mitarbeiter, Kategorien, Organigramm
Self-Join für Baumstrukturen
Vergleiche Preise, Duplikate, Nachbarn
Daten innerhalb einer Tabelle vergleichen
Rekursiv WITH RECURSIVE für tiefe Bäume
Alle Ebenen einer Hierarchie

Quick Summary

Self-Join
Tabelle mit sich selbst
Hierarchie
Mitarbeiter-Vorgesetzte
Kategorien
Baumstrukturen
RECURSIVE
Tiefe Bäume
Vergleiche
Daten vergleichen
Best Practices
Indizes, Aliase
SELECT e.name, m.name FROM employees e LEFT JOIN employees m ON e.manager_id = m.id;