Vai al contenuto
eLearner.app
Modulo 5 · Lezione 2 di 418/57 nel corso~10 min
Lezioni del modulo (2/4)

Self-JOIN: una tabella con se stessa

A volte la relazione che vuoi attraversare non è fra due tabelle diverse, ma fra righe della stessa tabella. Tipico esempio: una gerarchia "categoria → sotto-categoria", dove sia il padre sia il figlio sono righe della stessa tabella categories, legate da un parent_id.

Per leggere insieme una riga (figlio) e la "sua" altra riga (padre) si usa un self-JOIN: la stessa tabella appare due volte nella FROM, con due alias diversi.

La sintassi

SQL
SELECT colonne
FROM   tabella AS alias_a
JOIN   tabella AS alias_b ON alias_a.foreign_key = alias_b.id;

L'alias è obbligatorio: se omettiamo AS a / AS b, PostgreSQL non sa quale delle due copie intendiamo quando scriviamo name o id.

SQL
-- Ogni sotto-categoria con il nome della categoria padre:
SELECT child.name  AS subcategory,
       parent.name AS category
FROM   categories AS child
JOIN   categories AS parent ON child.parent_id = parent.id;

Letta in italiano: "prendi le righe di categories chiamate child, e per ognuna trova la riga di categories chiamata parent la cui id è il parent_id del child".

Il risultato esclude le categorie-radice (quelle con parent_id = NULL) perché un INNER JOIN salta le righe senza match. Se le vuoi includere, usa LEFT JOIN.

Self-JOIN con LEFT per "tenere anche le radici"

SQL
-- Tutte le categorie, con nome del padre o NULL se sono radice:
SELECT child.name AS category,
       parent.name AS parent
FROM   categories AS child
LEFT JOIN categories AS parent ON child.parent_id = parent.id;

Ora ottieni 9 righe (tutte le categorie del dataset), con parent = NULL per le 3 radici.

Aggregare con self-JOIN

Si combina benissimo con GROUP BY per "quante figlie ha ogni padre":

SQL
SELECT p.name AS parent_category,
       COUNT(c.id) AS children
FROM   categories AS p
LEFT JOIN categories AS c ON c.parent_id = p.id
WHERE  p.parent_id IS NULL          -- solo le radici
GROUP BY p.name
ORDER BY p.name;

Nota due dettagli importanti:

  1. WHERE p.parent_id IS NULL filtra prima del JOIN: prendiamo solo le radici come "tabella di sinistra".
  2. COUNT(c.id) (non COUNT(*)) restituisce 0 per le radici senza figli, mentre COUNT(*) ne restituirebbe 1 grazie alla riga LEFT JOIN con NULL.

Prova tu

Esercizio#sql.m5.l2.e1
Tentativi: 0Caricamento…

Per ogni sotto-categoria (categorie con parent_id NOT NULL), mostra il nome della sotto-categoria e il nome della sua categoria-padre. Due colonne: subcategory, category. Ordina per category poi per subcategory.

Caricamento editor…
Mostra suggerimento

La condizione del self-JOIN è child.parent_id = parent.id.

Soluzione disponibile dopo 3 tentativi

Esercizio di ripasso

Esercizio#sql.m5.l2.e2
Tentativi: 0Caricamento…

Per ogni categoria-radice (parent_id IS NULL), mostra il nome e il numero di sotto-categorie. Due colonne: name, children. Ordina per name. Includi una eventuale radice senza figlie con 0.

Caricamento editor…
Mostra suggerimento

LEFT JOIN su categories due volte (alias p per padre, c per figlio). Usa COUNT(c.id), non COUNT(*), per ottenere 0 sulle radici senza figli.

Soluzione disponibile dopo 3 tentativi