Salta el contingut

JOINs

Què és un JOIN?

Un JOIN combina files de dues o mes taules basant-se en una condició de jointura (normalment la relació entre una FK i una PK). El JOIN és el mecanisme que permet recuperar informació distribuïda entre múltiples taules d'un esquema normalitzat.

Cada tipus de JOIN retorna un conjunt diferent de files. La visualització clàssica usa cercles superposats: la part ressaltada en verd indica les files que apareixen al resultat.

INNER JOIN
A B
Coincidències en les dues taules
LEFT JOIN
A B
Tot A + coincidències de B (NULL si no n'hi ha)
RIGHT JOIN
A B
Coincidències d'A + tot B (NULL si no n'hi ha)
FULL OUTER JOIN
A B
Tot A + tot B (NULL on no hi ha coincidència)
LEFT ANTI JOIN
A B
Files d'A sense cap coincidència a B (LEFT JOIN + WHERE b.id IS NULL)
CROSS JOIN
A B A×B
Totes les combinacions (M files × N files)

INNER JOIN

El INNER JOIN (o simplement JOIN) retorna només les files que tenen correspondència en les dues taules. Les files que no coincideixen s'exclouen del resultat.

-- Nom del client i les seves comandes
SELECT
    c.nom || ' ' || c.cognoms AS client,
    co.id_comanda,
    co.data_com,
    co.total
FROM clients c
INNER JOIN comandes co ON c.id_client = co.id_client
ORDER BY c.cognoms, co.data_com DESC;

-- Tres taules
SELECT
    c.nom || ' ' || c.cognoms AS client,
    co.id_comanda,
    p.nom AS producte,
    lc.quantitat,
    lc.preu_unitari,
    lc.quantitat * lc.preu_unitari AS subtotal
FROM clients c
JOIN comandes co ON c.id_client = co.id_client
JOIN linies_comanda lc ON co.id_comanda = lc.id_comanda
JOIN productes p ON lc.id_producte = p.id_producte
WHERE co.estat = 'confirmada'
ORDER BY co.id_comanda, lc.num_linia;
SELECT
    CONCAT(c.nom, ' ', c.cognoms) AS client,
    co.id_comanda,
    co.data_com,
    co.total
FROM clients c
INNER JOIN comandes co ON c.id_client = co.id_client
ORDER BY c.cognoms, co.data_com DESC;
SELECT
    c.nom + ' ' + c.cognoms AS client,
    co.id_comanda,
    co.data_com,
    co.total
FROM clients c
INNER JOIN comandes co ON c.id_client = co.id_client
ORDER BY c.cognoms, co.data_com DESC;
SELECT
    c.nom || ' ' || c.cognoms AS client,
    co.id_comanda,
    co.data_com,
    co.total
FROM clients c
INNER JOIN comandes co ON c.id_client = co.id_client
ORDER BY c.cognoms, co.data_com DESC;

-- Tres taules
SELECT
    c.nom || ' ' || c.cognoms AS client,
    co.id_comanda,
    p.nom AS producte,
    lc.quantitat,
    lc.preu_unitari,
    lc.quantitat * lc.preu_unitari AS subtotal
FROM clients c
JOIN comandes co ON c.id_client = co.id_client
JOIN linies_comanda lc ON co.id_comanda = lc.id_comanda
JOIN productes p ON lc.id_producte = p.id_producte
WHERE co.estat = 'confirmada'
ORDER BY co.id_comanda, lc.num_linia;

INNER JOIN vs JOIN

JOIN sense qualificador és equivalent a INNER JOIN. La majoria de programadors professionals escriuen simplement JOIN per concisió.

Visualització pas a pas: com processa el motor un JOIN

Com funciona un JOIN — fila a fila
Pas 1 Punt de partida: dues taules independents. clients té 3 files; comandes en té 4. Volem combinar-les per saber quines comandes pertanyen a cada client.
clients
id_clientnom
1Joan
2Maria
3Pere
+
comandes
id_comid_clienttotal
101150 €
102130 €
103380 €
104315 €
Clau de jointura: clients.id_client = comandes.id_client Maria (id=2) no apareix a comandes
Pas 2 — INNER JOIN El motor compara cada fila de clients amb les files de comandes. Joan (id=1) té dues comandes coincidents: 101 i 102. Ambdues files apareixen al resultat.
clients
id_clientnom
1Joan
2Maria
3Pere
Resultat parcial
nomid_comtotal
Joan10150 €
Joan10230 €
Pas 3 — INNER JOIN Maria (id=2) no té cap comanda a la taula comandes. En un INNER JOIN, les files sense coincidència s'exclouen del resultat. Maria desapareix.
clients
id_clientnom
1Joan
2Maria
3Pere
Resultat parcial
nomid_comtotal
Joan10150 €
Joan10230 €
INNER JOIN: Maria s'exclou perque no té cap comanda
Pas 4 — INNER JOIN (resultat final) Pere (id=3) té dues comandes (103 i 104). El resultat final de l'INNER JOIN conté 4 files: les 2 de Joan i les 2 de Pere. Maria no hi apareix.
INNER JOIN — resultat final
nomid_comtotal
Joan10150 €
Joan10230 €
Pere10380 €
Pere10415 €
4 files al resultat (Joan x2 + Pere x2) Maria (sense comandes) no apareix
Pas 5 — LEFT JOIN (compara) Amb un LEFT JOIN, Maria sí que apareix al resultat però amb NULL als camps de comandes. El LEFT JOIN garanteix que totes les files de la taula esquerra surten sempre.
LEFT JOIN — resultat final
nomid_comtotal
Joan10150 €
Joan10230 €
MariaNULLNULL
Pere10380 €
Pere10415 €
5 files al resultat (tota la taula clients + comandes o NULL) Maria apareix amb NULL — cap comanda pero no s'exclou

LEFT JOIN (LEFT OUTER JOIN)

El LEFT JOIN retorna totes les files de la taula esquerra (LEFT), i les files coincidents de la taula dreta. On no hi ha coincidència, les columnes de la taula dreta contenen NULL.

flowchart LR
    subgraph Resultado
        A["Files de A\n(totes)"]
        B["Files de B\n(només coincidents\no NULL)"]
    end
    A --- B
-- Tots els clients, tinguin o no comandes
SELECT
    c.nom || ' ' || c.cognoms AS client,
    c.email,
    COUNT(co.id_comanda) AS num_comandes,
    COALESCE(SUM(co.total), 0) AS total_gastat
FROM clients c
LEFT JOIN comandes co ON c.id_client = co.id_client
GROUP BY c.id_client, c.nom, c.cognoms, c.email
ORDER BY total_gastat DESC;

-- Clients SENSE cap comanda (registres orfes)
SELECT c.nom, c.cognoms, c.email
FROM clients c
LEFT JOIN comandes co ON c.id_client = co.id_client
WHERE co.id_comanda IS NULL;
SELECT
    CONCAT(c.nom, ' ', c.cognoms) AS client,
    c.email,
    COUNT(co.id_comanda) AS num_comandes,
    COALESCE(SUM(co.total), 0) AS total_gastat
FROM clients c
LEFT JOIN comandes co ON c.id_client = co.id_client
GROUP BY c.id_client, c.nom, c.cognoms, c.email
ORDER BY total_gastat DESC;
SELECT
    c.nom + ' ' + c.cognoms AS client,
    c.email,
    COUNT(co.id_comanda) AS num_comandes,
    COALESCE(SUM(co.total), 0) AS total_gastat
FROM clients c
LEFT JOIN comandes co ON c.id_client = co.id_client
GROUP BY c.id_client, c.nom, c.cognoms, c.email
ORDER BY total_gastat DESC;
SELECT
    c.nom || ' ' || c.cognoms AS client,
    c.email,
    COUNT(co.id_comanda) AS num_comandes,
    COALESCE(SUM(co.total), 0) AS total_gastat
FROM clients c
LEFT JOIN comandes co ON c.id_client = co.id_client
GROUP BY c.id_client, c.nom, c.cognoms, c.email
ORDER BY total_gastat DESC;

-- Clients SENSE cap comanda
SELECT c.nom, c.cognoms, c.email
FROM clients c
LEFT JOIN comandes co ON c.id_client = co.id_client
WHERE co.id_comanda IS NULL;

RIGHT JOIN (RIGHT OUTER JOIN)

El RIGHT JOIN és el simètric del LEFT JOIN: retorna totes les files de la taula dreta (RIGHT), i les coincidents de l'esquerra. En la pràctica, s'usa poc; quasi sempre es prefereix reescriure com un LEFT JOIN canviant l'ordre de les taules.

-- Totes les comandes amb les dades del client (si existeix)
SELECT co.id_comanda, co.data_com, c.nom, c.cognoms
FROM clients c
RIGHT JOIN comandes co ON c.id_client = co.id_client;
-- Equivalent a:
SELECT co.id_comanda, co.data_com, c.nom, c.cognoms
FROM comandes co
LEFT JOIN clients c ON co.id_client = c.id_client;
SELECT co.id_comanda, co.data_com, c.nom, c.cognoms
FROM clients c
RIGHT JOIN comandes co ON c.id_client = co.id_client;
SELECT co.id_comanda, co.data_com, c.nom, c.cognoms
FROM clients c
RIGHT JOIN comandes co ON c.id_client = co.id_client;
SELECT co.id_comanda, co.data_com, c.nom, c.cognoms
FROM clients c
RIGHT JOIN comandes co ON c.id_client = co.id_client;

FULL OUTER JOIN

El FULL OUTER JOIN retorna totes les files de les dues taules, amb NULL on no hi ha coincidència.

SELECT
    c.nom AS client,
    co.id_comanda,
    co.data_com
FROM clients c
FULL OUTER JOIN comandes co ON c.id_client = co.id_client
WHERE c.id_client IS NULL OR co.id_comanda IS NULL;
-- Retorna clients sense comandes I comandes sense client (orfenat)
-- MySQL no suporta FULL OUTER JOIN directament
-- Emulació amb UNION:
SELECT c.nom, co.id_comanda
FROM clients c LEFT JOIN comandes co ON c.id_client = co.id_client
UNION
SELECT c.nom, co.id_comanda
FROM clients c RIGHT JOIN comandes co ON c.id_client = co.id_client;
SELECT c.nom, co.id_comanda, co.data_com
FROM clients c
FULL OUTER JOIN comandes co ON c.id_client = co.id_client;
SELECT c.nom AS client, co.id_comanda, co.data_com
FROM clients c
FULL OUTER JOIN comandes co ON c.id_client = co.id_client
WHERE c.id_client IS NULL OR co.id_comanda IS NULL;

CROSS JOIN

El CROSS JOIN (producte cartesià) combina cada fila de la primera taula amb cada fila de la segona, sense cap condició de jointura. Si la taula A té M files i B té N files, el resultat té M × N files.

Usos típics: generar combinacions per a preus, horaris, tests de càrrega.

-- Totes les combinacions de colors i talles (per a un sistema de roba)
SELECT c.nom AS color, t.nom AS talla
FROM colors c
CROSS JOIN talles t;
-- Si hi ha 5 colors i 6 talles → 30 files al resultat

CROSS JOIN i l'explosió combinatoria

Un CROSS JOIN accidental (condició ON incorrecta o absent) pot generar milions de files i bloquejar el servidor. Sempre verifiqueu que les vostres condicions JOIN són correctes en les vostres consultes.


Self JOIN (AUTO-JOIN)

Un self JOIN és un JOIN d'una taula consigo mateixa. Útil per a relacions jeràrquiques (empleats i els seus managers, categories i subcategories).

-- Empleats i els seus caps directes (auto-referència)
-- Taula: empleats(id_empleat, nom, id_manager)
SELECT
    e.nom AS empleat,
    m.nom AS manager
FROM empleats e
LEFT JOIN empleats m ON e.id_manager = m.id_empleat
ORDER BY m.nom, e.nom;
SELECT
    e.nom AS empleat,
    m.nom AS manager
FROM empleats e
LEFT JOIN empleats m ON e.id_manager = m.id_empleat
ORDER BY m.nom, e.nom;
SELECT
    e.nom AS empleat,
    m.nom AS manager
FROM empleats e
LEFT JOIN empleats m ON e.id_manager = m.id_empleat
ORDER BY m.nom, e.nom;
SELECT
    e.nom AS empleat,
    m.nom AS manager
FROM empleats e
LEFT JOIN empleats m ON e.id_manager = m.id_empleat
ORDER BY m.nom, e.nom;

Resum comparatiu

JOIN Files retornades Cas d'ús típic NULL?
INNER JOIN Coincidències d'ambdues taules Llistat complet amb dades relacionades No
LEFT JOIN Tot A + coincidències de B Clients amb o sense comandes Sí (B)
RIGHT JOIN Coincidències d'A + tot B Equivalent a LEFT amb ordre de taules intercanviat Sí (A)
FULL OUTER JOIN Tot A + tot B Detectar orfes en qualsevol de les dues taules Sí (ambdós)
CROSS JOIN Totes les combinacions (M × N) Generar combinacions (colors × talles, etc.) No
LEFT + WHERE b.id IS NULL Files d'A sense cap coincidència Clients que mai han fet cap comanda

Truc professional

En la pràctica, el RIGHT JOIN gairebé mai s'usa: sempre és possible reescriure'l com un LEFT JOIN intercanviant l'ordre de les taules, cosa que millora la llegibilitat. La taula "principal" hauria de ser sempre la de l'esquerra.


AC0372/04/04 — Miniactivitat

RA4 · CA4.3, CA4.6

Amb la vostra BD de pràctiques, escriviu:

  1. JOIN que mostri totes les reserves amb el nom d'usuari i el nom de la instal·lació.
  2. LEFT JOIN que llisti tots els usuaris i el nombre de reserves que han fet (inclou els que no n'han feta cap).
  3. Usuaris que mai han fet cap reserva (usant LEFT JOIN + WHERE IS NULL).
  4. Reserves del mes actual amb totes les dades: usuari, instal·lació, data, hora i preu calculat.
  5. La instal·lació que s'ha reservat mes vegades (GROUP BY + JOIN + ORDER BY + LIMIT 1).