A la lliçó anterior vas perdre tres clients, tres productes i deu comandes sense que ningú t'avisés. El LEFT JOIN és l'eina que els recupera. La seva regla és una sola frase: conserva totes les files de la taula esquerra, casin o no casin amb la dreta; quan no casen, les columnes de la dreta s'omplen amb NULL.

Sona senzill, i ho és. Però el LEFT JOIN amaga el parany més famós de tot l'SQL intermedi: posar una condició a l'ON o al WHERE deixa de ser indiferent. La diferència no dona error, no dona avís, i produeix dos resultats diferents que semblen igualment plausibles. Mitja lliçó està dedicada al fet que no hi caiguis mai.

Contingut

  1. La regla del LEFT JOIN i el diagrama de conjunts
  2. LEFT JOIN = LEFT OUTER JOIN
  3. D'on surten exactament els NULL
  4. Els tres casos reals de BotigaVerda
  5. El patró anti-join: trobar el que no casa
  6. El parany: condició a l'ON enfront de condició al WHERE
  7. LEFT JOIN encadenats
  8. Quan la taula dreta té diverses files per cada esquerra
  9. Errors habituals i consells
  10. Exercicis
  11. Conclusió

  1. La regla del LEFT JOIN i el diagrama de conjunts

flowchart LR
    subgraph R[" "]
        direction LR
        A(("només a l'esquerra<br/>✅ es conserva<br/>amb NULL a la dreta"))
        I(("casen<br/>✅ resultat"))
        B(("només a la dreta<br/>❌ fora"))
    end
    style A fill:#d9f0d9,stroke:#2b7a2b,stroke-width:3px
    style I fill:#d9f0d9,stroke:#2b7a2b,stroke-width:3px
    style B fill:#f8f8f8,stroke:#bbb,stroke-dasharray: 4 4

Com a flux de files, ampliant el diagrama de 03-02:

flowchart LR
    A["taula esquerra"] --> C["cartesià"]
    B["taula dreta"] --> C
    C --> D["filtre ON"]
    D --> E["✅ files que casen"]
    D --> F["files esquerres<br/>sense parella"]
    F --> G["✅ s'afegeixen igualment<br/>amb NULL a la dreta"]
    D --> H["❌ files dretes<br/>sense parella: es descarten"]

Aquest pas extra —"s'afegeixen igualment amb NULL a la dreta"— és el pas 1d de l'ordre lògic que vas veure a 03-01. Passa dins del FROM, després d'aplicar l'ON i abans del WHERE. Tot el que explica aquesta lliçó es dedueix d'aquest fet.

La paraula "esquerra" és literal: és la taula que escrius abans de la paraula LEFT JOIN.

FROM clients AS c              -- ← l'esquerra: es conserva sencera
LEFT JOIN comandes AS co       -- ← la dreta: només aporta el que casi
  ON co.client_id = c.id

D'aquí en surt la conseqüència més important per escriure bé: el LEFT JOIN no és commutatiu. clients LEFT JOIN comandes conserva els 15 clients; comandes LEFT JOIN clients conserva les 20 comandes. Són preguntes diferents.

Com triar el costat esquerre: digues-te la pregunta de negoci en veu alta i busca el substantiu que ha d'aparèixer complet a l'informe. "Vull tots els clients, amb les seves comandes si en tenen" → clients és l'esquerra. "Vull totes les comandes, amb el seu comercial si en tenen" → comandes és l'esquerra.

  1. LEFT JOIN = LEFT OUTER JOIN

Les dues escriptures són idèntiques:

FROM clients AS c LEFT JOIN       comandes AS co ON co.client_id = c.id
FROM clients AS c LEFT OUTER JOIN comandes AS co ON co.client_id = c.id

OUTER és opcional i pràcticament ningú no l'escriu. L'adjectiu extern (outer) descriu tota la família: LEFT, RIGHT i FULL són joins externs perquè conserven files que queden fora de l'emparellament; INNER és el join intern perquè només retorna el que queda dins.

Escriptura Equival a Freqüència real
LEFT JOIN LEFT OUTER JOIN L'habitual
LEFT OUTER JOIN LEFT JOIN Poc freqüent, una mica més en documentació formal

En aquest curs escrivim LEFT JOIN.

  1. D'on surten exactament els NULL

Aquest punt es malinterpreta sovint, així que convé ser precisos: els NULL que apareixen en un LEFT JOIN no eren a la base de dades. Els fabrica el motor en el moment de construir el resultat.

Quan una fila de l'esquerra no troba parella, PostgreSQL la incorpora igualment al resultat i omple totes les columnes de la taula dreta amb NULL. No només la de la clau: totes.

flowchart LR
    A["client 13<br/>Núria Bosch Ferrer"] --> B{"hi ha alguna comanda<br/>amb client_id = 13?"}
    B -->|"no"| C["fila conservada<br/>co.id = NULL<br/>co.data_comanda = NULL<br/>co.estat = NULL<br/>co.despeses_enviament = NULL"]

D'aquí en surten dues conseqüències que faràs servir constantment:

  1. Pots detectar l'absència mirant qualsevol columna de la dreta. Si co.id IS NULL en un LEFT JOIN des de clients, és que no hi va haver parella. És la base de l'anti-join de la secció 5.
  2. Convé mirar una columna NOT NULL de la dreta. Si triessis una columna que pot ser nul·la de debò a les dades, no sabries distingir "no hi va haver parella" de "hi va haver parella i aquella dada era buida". Per això l'anti-join s'escriu sempre contra la clau primària de la taula dreta: co.id, lc.id, r.id. Una PK mai no és NULL de forma legítima.

Un tercer efecte, més subtil, és l'aritmètica amb nuls que vas veure a 02-02: qualsevol operació amb NULL dona NULL. Si calcules co.despeses_enviament * 2 sobre una fila sense parella, el resultat és NULL, no zero. La funció COALESCE que ho resol s'estudia a 06-04; el tractament complet dels nuls, a 04-03.

  1. Els tres casos reals de BotigaVerda

Els tres buits deliberats del conjunt de dades existeixen precisament per a aquesta lliçó.

4.1. Tots els clients amb les seves comandes

SELECT c.id AS client_id,
       c.nom || ' ' || c.cognoms AS client,
       c.ciutat,
       co.id AS comanda_id,
       co.data_comanda,
       co.estat
FROM clients AS c
LEFT JOIN comandes AS co ON co.client_id = c.id
ORDER BY c.id, co.id;
client_id client ciutat comanda_id data_comanda estat
1 Lucía Martínez Soler València 1 2025-03-04 lliurat
1 Lucía Martínez Soler València 5 2025-05-07 lliurat
1 Lucía Martínez Soler València 15 2025-12-02 lliurat
2 Carlos Ferrer Ibáñez València 2 2025-03-12 lliurat
2 Carlos Ferrer Ibáñez València 11 2025-09-09 lliurat
3 Marta Sanchis Gil Castelló 3 2025-04-02 lliurat
4 Javier Ortega Ruiz Madrid 4 2025-04-19 lliurat
4 Javier Ortega Ruiz Madrid 16 2025-12-19 enviat
5 Ana Belmonte Roca Barcelona 6 2025-05-23 cancellat
5 Ana Belmonte Roca Barcelona 18 2026-01-27 pagat
6 Pau Llorens Vidal València 7 2025-06-11 lliurat
6 Pau Llorens Vidal València 19 2026-02-09 pagat
7 Sofia Moreira Costa Lisboa 8 2025-06-28 lliurat
7 Sofia Moreira Costa Lisboa 17 2026-01-13 enviat
8 Tiago Almeida Nunes Porto 9 2025-07-15 lliurat
9 Camille Dubois Lió 10 2025-08-03 lliurat
9 Camille Dubois Lió 20 2026-02-21 pendent
10 Julien Moreau París 12 2025-10-01 lliurat
11 Elena Navarro Puig Alacant 13 2025-10-22 lliurat
12 Diego Ramos Herrera Sevilla 14 2025-11-14 lliurat
13 Núria Bosch Ferrer Barcelona (null) (null) (null)
14 Hugo Iglesias Pardo Saragossa (null) (null) (null)
15 Inés Carrasco Vega València (null) (null) (null)

23 files. Compara amb 03-02:

Consulta Files Composició
clients INNER JOIN comandes 20 Només comandes reals
clients LEFT JOIN comandes 23 20 comandes + 3 clients sense comandes

Ja hi són la Núria, l'Hugo i la Inés, amb tota la part de comandes a NULL. L'aritmètica és exacta i val la pena interioritzar-la: el resultat d'un LEFT JOIN té tantes files com l'INNER JOIN més una fila per cada fila esquerra òrfena.

4.2. Tots els productes amb les seves línies de venda

Mateix patró, ara al catàleg. Retallem a cinc productes per veure el contrast amb claredat:

SELECT p.id AS producte_id,
       p.nom AS producte,
       lc.id AS linia_id,
       lc.comanda_id,
       lc.quantitat
FROM productes AS p
LEFT JOIN linies_comanda AS lc ON lc.producte_id = p.id
WHERE p.id IN (12, 13, 18, 19, 20)
ORDER BY p.id, lc.id;
producte_id producte linia_id comanda_id quantitat
12 Bosses reutilitzables de cotó (pack 5) 13 5 1
12 Bosses reutilitzables de cotó (pack 5) 31 13 2
12 Bosses reutilitzables de cotó (pack 5) 40 16 1
13 Espelmes de cera de soja (pack 2) (null) (null) (null)
18 Raspall de dents de bambú 23 9 4
18 Raspall de dents de bambú 32 13 3
18 Raspall de dents de bambú 47 20 2
19 Desodorant natural en barra 50 g (null) (null) (null)
20 Càpsules d'espirulina 120 u (null) (null) (null)

Els productes 12 i 18 s'han venut tres vegades cadascun i ocupen tres files. Els productes 13, 19 i 20 no s'han venut mai i ocupen una fila amb tot a NULL.

Sense el WHERE de retall, la consulta completa retorna 50 files: les 47 de linies_comanda més les 3 dels productes mai venuts.

4.3. Totes les comandes amb el seu comercial

El tercer cas és diferent dels dos anteriors, i convé fixar-se en el matís. Aquí no falta la fila filla: la clau forana és NULL.

SELECT co.id AS comanda_id,
       co.data_comanda,
       co.estat,
       e.nom || ' ' || e.cognoms AS comercial
FROM comandes AS co
LEFT JOIN empleats AS e ON co.empleat_id = e.id
ORDER BY co.id;
comanda_id data_comanda estat comercial
1 2025-03-04 lliurat (null)
2 2025-03-12 lliurat Óscar Peris Blasco
3 2025-04-02 lliurat (null)
4 2025-04-19 lliurat Laia Puig Sanchis
5 2025-05-07 lliurat (null)
6 2025-05-23 cancellat Óscar Peris Blasco
7 2025-06-11 lliurat (null)
8 2025-06-28 lliurat Laia Puig Sanchis
9 2025-07-15 lliurat (null)
10 2025-08-03 lliurat Óscar Peris Blasco
11 2025-09-09 lliurat (null)
12 2025-10-01 lliurat Laia Puig Sanchis
13 2025-10-22 lliurat (null)
14 2025-11-14 lliurat Marc Estévez Roig
15 2025-12-02 lliurat (null)
16 2025-12-19 enviat Óscar Peris Blasco
17 2026-01-13 enviat (null)
18 2026-01-27 pagat Laia Puig Sanchis
19 2026-02-09 pagat (null)
20 2026-02-21 pendent Marc Estévez Roig

20 files: les 20 comandes. Enfront de les 10 que retornava l'INNER JOIN de 03-02. Els NULL de la columna comercial signifiquen aquí una cosa perfectament llegible per al negoci: comanda entrada per la web, sense comercial assignat. Deu de vint, exactament la proporció que 01-06 descrivia.

Fixa't en un detall: comercial val NULL per partida doble. Primer perquè co.empleat_id és NULL i no casa amb res; i segon perquè, encara que casés, la concatenació e.nom || ' ' || e.cognoms sobre columnes nul·les dona NULL (el parany de 02-02).

Resum dels tres casos

Cas Esquerra Dreta Files Què recupera
Clients i comandes clients (15) comandes 23 Clients 13, 14, 15
Productes i vendes productes (20) linies_comanda 50 Productes 13, 19, 20
Comandes i comercial comandes (20) empleats 20 Les 10 comandes web

  1. El patró anti-join: trobar el que no casa

Fins ara hem fet servir el LEFT JOIN per conservar el que no casa. Ara el farem servir per quedar-nos només amb això, que és una de les consultes més demanades a qualsevol empresa: clients inactius, productes sense rotació, factures sense cobrar, usuaris sense verificar.

La tècnica es diu anti-join i s'escriu en dos moviments:

  1. Un LEFT JOIN que conserva tot el de l'esquerra.
  2. Un WHERE <clau primària de la dreta> IS NULL que es queda només amb les files que no van trobar parella.
flowchart LR
    A["LEFT JOIN<br/>totes les esquerres"] --> B["files amb parella<br/>co.id té valor"]
    A --> C["files sense parella<br/>co.id IS NULL"]
    B --> D["❌ descartades pel WHERE"]
    C --> E["✅ el resultat que busquem"]

5.1. Quins clients no han comprat mai

SELECT c.id,
       c.nom,
       c.cognoms,
       c.email,
       c.ciutat,
       c.data_registre
FROM clients AS c
LEFT JOIN comandes AS co ON co.client_id = c.id
WHERE co.id IS NULL
ORDER BY c.id;
id nom cognoms email ciutat data_registre
13 Núria Bosch Ferrer [email protected] Barcelona 2025-06-20
14 Hugo Iglesias Pardo [email protected] Saragossa 2025-09-12
15 Inés Carrasco Vega [email protected] València 2026-01-08

Tres files. Aquesta és exactament la llista que demanaria el departament de màrqueting per llançar una campanya de primera compra.

5.2. Quins productes no s'han venut mai

SELECT p.id,
       p.nom,
       p.preu,
       p.stock,
       p.actiu
FROM productes AS p
LEFT JOIN linies_comanda AS lc ON lc.producte_id = p.id
WHERE lc.id IS NULL
ORDER BY p.id;
id nom preu stock actiu
13 Espelmes de cera de soja (pack 2) 13.75 0 true
19 Desodorant natural en barra 50 g 7.80 75 true
20 Càpsules d'espirulina 120 u 16.40 55 false

Tres files, i amb un diagnòstic diferent per a cadascuna: les espelmes no es venen perquè no hi ha estoc; l'espirulina perquè està descatalogada (actiu = false); el desodorant té estoc i està actiu, així que el seu problema és comercial. Un JOIN ben plantejat no només retorna dades: apunta a la causa.

5.3. Les tres regles de l'anti-join

Regla Per què
El JOIN ha de ser LEFT (o RIGHT), mai INNER Un INNER JOIN ja ha descartat les files sense parella: WHERE ... IS NULL retornaria sempre 0 files
La condició IS NULL va al WHERE, mai a l'ON A l'ON formaria part de l'emparellament i no filtraria res d'útil
La columna comprovada ha de ser NOT NULL a la taula dreta Si triessis una columna que admet nuls, confondries "sense parella" amb "parella amb la dada buida". Fes servir sempre la seva clau primària

Sobre IS NULL: és l'operador correcte per preguntar per nuls, perquè = NULL mai no és cert (ho vas veure a 02-03 amb empleat_id = NULL retornant 0 files). El seu tractament complet, juntament amb IS NOT NULL i COALESCE, és la lliçó 04-03.

Nota: al mòdul 7 veuràs dues altres formes d'escriure el mateix: NOT EXISTS amb una subconsulta correlacionada, i NOT IN. Totes tres tenen el mateix objectiu i diferent comportament davant dels nuls. L'anti-join amb LEFT JOIN és el que pots escriure avui, i és perfectament idiomàtic.

  1. El parany: condició a l'ON enfront de condició al WHERE

Aquí hi ha el contingut més important de la lliçó. Para atenció a la pregunta, perquè la clau és allà:

"Dona'm tots els clients, amb les seves comandes lliurades."

Fixa't en el "tots": l'informe ha de llistar els 15 clients, tinguin o no comandes lliurades. L'escriurem de les dues maneres possibles.

Versió A: la condició a l'ON

-- ✅ CORRECTA per a la pregunta plantejada
SELECT c.id AS client_id,
       c.nom || ' ' || c.cognoms AS client,
       co.id AS comanda_id,
       co.data_comanda,
       co.estat
FROM clients AS c
LEFT JOIN comandes AS co
  ON co.client_id = c.id
 AND co.estat = 'lliurat'
ORDER BY c.id, co.id;
client_id client comanda_id data_comanda estat
1 Lucía Martínez Soler 1 2025-03-04 lliurat
1 Lucía Martínez Soler 5 2025-05-07 lliurat
1 Lucía Martínez Soler 15 2025-12-02 lliurat
2 Carlos Ferrer Ibáñez 2 2025-03-12 lliurat
2 Carlos Ferrer Ibáñez 11 2025-09-09 lliurat
3 Marta Sanchis Gil 3 2025-04-02 lliurat
4 Javier Ortega Ruiz 4 2025-04-19 lliurat
5 Ana Belmonte Roca (null) (null) (null)
6 Pau Llorens Vidal 7 2025-06-11 lliurat
7 Sofia Moreira Costa 8 2025-06-28 lliurat
8 Tiago Almeida Nunes 9 2025-07-15 lliurat
9 Camille Dubois 10 2025-08-03 lliurat
10 Julien Moreau 12 2025-10-01 lliurat
11 Elena Navarro Puig 13 2025-10-22 lliurat
12 Diego Ramos Herrera 14 2025-11-14 lliurat
13 Núria Bosch Ferrer (null) (null) (null)
14 Hugo Iglesias Pardo (null) (null) (null)
15 Inés Carrasco Vega (null) (null) (null)

18 files i els 15 clients presents. Els que no tenen cap comanda lliurada apareixen amb NULL: la Núria, l'Hugo i la Inés perquè no han comprat mai, i Ana Belmonte Roca perquè les seves dues comandes estan cancellat i pagat, cap de lliurada. L'Ana és el cas més interessant: existeix a comandes, però cap de les seves comandes no supera la condició de l'ON.

Versió B: la condició al WHERE

Canviem exactament dues paraules: AND passa a ser WHERE.

-- ⚠️ INCORRECTA per a la pregunta plantejada
SELECT c.id AS client_id,
       c.nom || ' ' || c.cognoms AS client,
       co.id AS comanda_id,
       co.data_comanda,
       co.estat
FROM clients AS c
LEFT JOIN comandes AS co ON co.client_id = c.id
WHERE co.estat = 'lliurat'
ORDER BY c.id, co.id;
client_id client comanda_id data_comanda estat
1 Lucía Martínez Soler 1 2025-03-04 lliurat
1 Lucía Martínez Soler 5 2025-05-07 lliurat
1 Lucía Martínez Soler 15 2025-12-02 lliurat
2 Carlos Ferrer Ibáñez 2 2025-03-12 lliurat
2 Carlos Ferrer Ibáñez 11 2025-09-09 lliurat
3 Marta Sanchis Gil 3 2025-04-02 lliurat
4 Javier Ortega Ruiz 4 2025-04-19 lliurat
6 Pau Llorens Vidal 7 2025-06-11 lliurat
7 Sofia Moreira Costa 8 2025-06-28 lliurat
8 Tiago Almeida Nunes 9 2025-07-15 lliurat
9 Camille Dubois 10 2025-08-03 lliurat
10 Julien Moreau 12 2025-10-01 lliurat
11 Elena Navarro Puig 13 2025-10-22 lliurat
12 Diego Ramos Herrera 14 2025-11-14 lliurat

14 files i només 12 clients. Han desaparegut l'Ana, la Núria, l'Hugo i la Inés. El LEFT JOIN s'ha comportat com un INNER JOIN.

Per què passa

Torna a l'ordre lògic de 03-01 i segueix el recorregut de la fila de la Núria a cada versió:

flowchart TD
    subgraph A["Versió A · condició a ON"]
        A1["FROM: cartesià"] --> A2["ON: client_id = 13<br/>I estat = 'lliurat'<br/>→ cap parella"]
        A2 --> A3["1d: s'afegeix la Núria<br/>amb comandes a NULL"]
        A3 --> A4["WHERE: no n'hi ha<br/>→ ✅ la Núria sobreviu"]
    end
    subgraph B["Versió B · condició a WHERE"]
        B1["FROM: cartesià"] --> B2["ON: client_id = 13<br/>→ cap parella"]
        B2 --> B3["1d: s'afegeix la Núria<br/>amb comandes a NULL"]
        B3 --> B4["WHERE: NULL = 'lliurat'<br/>no és TRUE<br/>→ ❌ la Núria s'elimina"]
    end

El pas 1d que afegeix la Núria a les dues versions. La diferència és després: a la versió B, el WHERE avalua co.estat = 'lliurat' sobre una fila el co.estat de la qual és NULL. I NULL = 'lliurat' no és FALSE, és NULL, que tampoc no és TRUE, així que la fila es descarta. És el mateix mecanisme de 02-03 actuant en un lloc nou.

La regla que cal gravar: en un LEFT JOIN, qualsevol condició al WHERE sobre una columna de la taula dreta converteix el LEFT en un INNER, perquè les files omplertes amb NULL no la poden satisfer.

Taula de decisió

On posar la condició sobre la taula dreta Efecte Quan la vols
A l'ON Restringeix què s'emparella. Les files esquerres sense parella es conserven amb NULL "Tots els X, amb els seus Y que compleixin Z"
Al WHERE Filtra el resultat final. El LEFT JOIN es degrada a INNER JOIN "Només els X que tinguin algun Y que compleixi Z"
Al WHERE, amb IS NULL Es queda només amb les files sense parella Anti-join: "els X que no tenen cap Y"

Les tres són vàlides: cadascuna respon a una pregunta diferent. L'error no és fer servir el WHERE, és fer-lo servir creient que fa el de l'ON.

Un cas que sí que és segur: una condició al WHERE sobre una columna de la taula esquerra no degrada res. WHERE c.pais = 'Espanya' filtra clients, i els que passin el filtre conserven el seu NULL a la dreta sense problema. El perill és exclusivament el de les columnes de la dreta.

  1. LEFT JOIN encadenats

Amb tres o més taules el LEFT JOIN manté la seva lògica, però apareix una regla que sorprèn: un INNER JOIN col·locat després d'un LEFT JOIN anul·la l'efecte del LEFT.

Volem "tots els clients, amb les seves comandes i el comercial de cada comanda". Escrit amb LEFT als dos salts:

-- ✅ CORRECTA: 23 files, els 15 clients
FROM clients   AS c
LEFT JOIN comandes AS co ON co.client_id  = c.id
LEFT JOIN empleats AS e  ON co.empleat_id = e.id

I ara el mateix camí amb un INNER JOIN al segon salt:

-- ⚠️ INCORRECTA: 10 files
FROM clients AS c
LEFT JOIN  comandes AS co ON co.client_id  = c.id
INNER JOIN empleats AS e  ON co.empleat_id = e.id
Consulta Files Què queda
LEFT + LEFT 23 Els 15 clients, les 20 comandes, amb NULL on falti
LEFT + INNER 10 Només les 10 comandes que tenen comercial

El raonament: els JOIN es resolen d'esquerra a dreta, així que (clients LEFT JOIN comandes) produeix 23 files; sobre aquestes 23, l'INNER JOIN amb empleats exigeix que co.empleat_id casi amb un empleat. Les 3 files de clients sense comandes tenen co.empleat_id = NULL i cauen; les 10 files de comandes web tenen empleat_id = NULL i també cauen. En queden 10.

Regla pràctica: un cop has obert una cadena amb LEFT JOIN, tots els salts posteriors sobre aquella branca han de ser LEFT JOIN. Un sol INNER al mig desfà tota la feina, i ho fa sense cap avís.

És un error molt fàcil de cometre en ampliar una consulta existent: algú afegeix JOIN productes ON ... al final d'una consulta que començava amb LEFT JOIN, i l'informe perd files d'un dia per l'altre sense que ningú toqui el LEFT.

  1. Quan la taula dreta té diverses files per cada esquerra

El LEFT JOIN no protegeix de la multiplicació de files de 03-02. Continua havent-hi una fila per parella:

SELECT p.id AS producte_id,
       p.nom AS producte,
       r.id AS ressenya_id,
       r.puntuacio
FROM productes AS p
LEFT JOIN ressenyes AS r ON r.producte_id = p.id
WHERE p.id IN (1, 2, 4, 6)
ORDER BY p.id, r.id;
producte_id producte ressenya_id puntuacio
1 Oli d'oliva verge extra 500 ml 1 5
1 Oli d'oliva verge extra 500 ml 7 5
2 Arròs integral ecològic 1 kg 2 4
2 Arròs integral ecològic 1 kg 10 5
4 Pasta d'espelta 500 g (null) (null)
6 Crema facial d'àloe vera 50 ml 3 5
6 Crema facial d'àloe vera 50 ml 9 4

Quatre productes produeixen set files: dues ressenyes per als productes 1, 2 i 6, i una fila buida per al 4. Sobre el catàleg sencer, productes LEFT JOIN ressenyes retorna 23 files: les 12 ressenyes més els 11 productes sense cap.

El compte general d'un LEFT JOIN és sempre aquest:

files del resultat = files que casen (INNER) + files esquerres òrfenes
Exemple Casen Òrfenes Total
clients LEFT JOIN comandes 20 3 23
productes LEFT JOIN linies_comanda 47 3 50
productes LEFT JOIN ressenyes 12 11 23
comandes LEFT JOIN empleats 10 10 20

I de nou l'avís del mòdul 4: si sobre el primer cas sumessis c.data_registre, o comptessis clients, obtindries xifres inflades, perquè la Lucía hi apareix tres vegades. Un LEFT JOIN conserva files; no les deduplica.

Errors habituals i consells

  • Posar una condició sobre la taula dreta al WHERE. Degrada el LEFT JOIN a INNER JOIN sense dir res. És *l'*error d'aquesta lliçó: 18 files contra 14, i quatre clients desapareguts.
  • Barrejar un INNER JOIN després d'un LEFT JOIN. Mateix efecte, en cadena: 23 files es converteixen en 10.
  • Escriure les taules a l'inrevés. comandes LEFT JOIN clients no és clients LEFT JOIN comandes. El LEFT JOIN no és commutatiu; l'esquerra és la que es conserva sencera.
  • Fer l'anti-join contra una columna que admet nuls. WHERE co.empleat_id IS NULL no distingeix "no hi va haver parella" de "hi va haver parella amb la dada buida". Compara sempre contra la clau primària de la dreta.
  • Intentar un anti-join amb INNER JOIN. INNER JOIN ... WHERE dreta.id IS NULL retorna sempre zero files: l'INNER ja ha llençat aquelles files.
  • Fer servir = NULL en lloc d'IS NULL. WHERE co.id = NULL retorna zero files sempre (02-03).
  • Suposar que NULL al resultat significa zero. co.despeses_enviament és NULL, no 0.00, per a un client sense comandes. Operar-hi propaga el nul (COALESCE, a 06-04).
  • Oblidar que el LEFT JOIN continua multiplicant files. Conserva els orfes, però no impedeix que un client amb tres comandes ocupi tres files.
  • Consell: escriu primer l'INNER JOIN, comprova el recompte, i després canvia'l a LEFT. La diferència entre tots dos recomptes et diu exactament quants orfes hi ha.
  • Consell: per a l'anti-join, digues la pregunta en veu alta. "Clients sense comandes", "productes mai venuts", "factures sense cobrar". El "sense" i el "mai" són el senyal que toca LEFT JOIN ... WHERE ... IS NULL.
  • Consell: sospita de qualsevol WHERE que anomeni la taula dreta d'un LEFT JOIN. Llevat que sigui un IS NULL deliberat, gairebé sempre hauria de ser a l'ON.

Exercicis

Exercici 1

L'equip de producte vol saber quines referències del catàleg no han rebut cap ressenya, per llançar una campanya de sol·licitud d'opinions. Escriu la consulta que retorni l'id, el nom, la categoria i el preu d'aquests productes, ordenats per id.

Després respon: per què el JOIN amb categories pot ser un INNER JOIN sense que això trenqui l'anti-join?

Exercici 2

Logística necessita el llistat complet de les 20 comandes amb la informació de la seva devolució, si n'hi va haver: id de la comanda, data, estat, despeses d'enviament i —quan hi hagi devolució— el seu motiu i import.

  1. Escriu la consulta.
  2. Escriu després la variant que retorna només les comandes que no van tenir devolució, i indica quantes files dona.

Exercici 3

Un company et passa aquesta consulta i et diu: "vull tots els clients amb les seves comandes pagades amb targeta, però em falten clients".

SELECT c.id, c.nom, co.id AS comanda_id, co.metode_pagament
FROM clients AS c
LEFT JOIN comandes AS co ON co.client_id = c.id
WHERE co.metode_pagament = 'targeta';
  1. Explica què està fent realment aquesta consulta.
  2. Corregeix-la perquè retorni el que ell vol.
  3. Prediu quantes files retorna cada versió i quants clients diferents apareixen a cadascuna.

Solucions

Solució 1

SELECT p.id,
       p.nom AS producte,
       cat.nom AS categoria,
       p.preu
FROM productes AS p
INNER JOIN categories AS cat ON p.categoria_id = cat.id
LEFT  JOIN ressenyes  AS r   ON r.producte_id  = p.id
WHERE r.id IS NULL
ORDER BY p.id;
id producte categoria preu
3 Mel de tarongina crua 500 g Alimentació 9.75
4 Pasta d'espelta 500 g Alimentació 2.80
7 Xampú sòlid de romaní 80 g Cosmètica natural 8.40
8 Oli corporal d'ametlles 200 ml Cosmètica natural 14.25
9 Bàlsam labial de calèndula 15 ml Cosmètica natural 4.60
11 Fregall vegetal de lufa (pack 3) Llar sostenible 5.50
13 Espelmes de cera de soja (pack 2) Llar sostenible 13.75
14 Infusió de camamilla ecològica 20 u Begudes 3.25
17 Suc de taronja premsat en fred 1 L Begudes 5.40
19 Desodorant natural en barra 50 g Higiene personal 7.80
20 Càpsules d'espirulina 120 u Complements 16.40

11 files, exactament els 11 productes sense ressenya que anunciava 01-06. Només 9 dels 20 productes han estat ressenyats alguna vegada.

Per què el JOIN amb categories pot ser INNER: perquè la secció 7 adverteix dels INNER JOIN que apareixen després d'un LEFT JOIN sobre la mateixa branca. Aquí no és el cas: categories penja de productes per una FK que apunta a una categoria que sempre existeix, així que aquell INNER JOIN no descarta cap producte. Les 20 files de partida continuen sent 20 abans d'aplicar el LEFT JOIN amb ressenyes. La regla precisa és: un INNER JOIN és segur mentre no pugui eliminar files de la taula protagonista.

(Si categoria_id admetés NULL amb dades reals nul·les, aquell INNER JOIN sí que perdria productes i caldria escriure'l també com a LEFT JOIN.)

Solució 2

1. Totes les comandes amb la seva devolució, si n'hi va haver:

SELECT co.id AS comanda_id,
       co.data_comanda,
       co.estat,
       co.despeses_enviament,
       d.motiu,
       d.import AS import_reemborsat
FROM comandes AS co
LEFT JOIN devolucions AS d ON d.comanda_id = co.id
ORDER BY co.id;

Retorna 20 files: les 3 amb devolució (comandes 6, 10 i 13) mostren motiu i import; les altres 17 mostren *(null)* en aquestes dues columnes.

2. Només les comandes sense devolució (anti-join):

SELECT co.id AS comanda_id,
       co.data_comanda,
       co.estat,
       co.despeses_enviament
FROM comandes AS co
LEFT JOIN devolucions AS d ON d.comanda_id = co.id
WHERE d.id IS NULL
ORDER BY co.id;
comanda_id data_comanda estat despeses_enviament
1 2025-03-04 lliurat 4.95
2 2025-03-12 lliurat 0.00
3 2025-04-02 lliurat 4.95
4 2025-04-19 lliurat 4.95
5 2025-05-07 lliurat 0.00
7 2025-06-11 lliurat 6.50
8 2025-06-28 lliurat 9.90
9 2025-07-15 lliurat 9.90
11 2025-09-09 lliurat 0.00
12 2025-10-01 lliurat 12.50
14 2025-11-14 lliurat 4.95
15 2025-12-02 lliurat 0.00
16 2025-12-19 enviat 4.95
17 2026-01-13 enviat 9.90
18 2026-01-27 pagat 4.95
19 2026-02-09 pagat 4.95
20 2026-02-21 pendent 12.50

17 files = 20 comandes − 3 devolucions. Falten justament les comandes 6, 10 i 13.

Solució 3

1. Què fa realment. La condició co.metode_pagament = 'targeta' és al WHERE i s'aplica a una columna de la taula dreta. Tota fila de client sense comanda arriba al WHERE amb co.metode_pagament a NULL, i NULL = 'targeta' no és TRUE. Resultat: el LEFT JOIN es degrada a INNER JOIN i la consulta retorna només els clients que tenen almenys una comanda pagada amb targeta. És una pregunta legítima, però no la que ell volia.

2. Correcció: moure la condició a l'ON.

-- ✅ CORRECTA
SELECT c.id,
       c.nom,
       co.id AS comanda_id,
       co.metode_pagament
FROM clients AS c
LEFT JOIN comandes AS co
  ON co.client_id = c.id
 AND co.metode_pagament = 'targeta'
ORDER BY c.id, co.id;

3. Predicció de files. Les comandes amb metode_pagament = 'targeta' són els ids 1, 3, 5, 6, 8, 10, 11, 13, 15, 16 i 19: 11 comandes, de 9 clients diferents (la Lucía hi apareix tres vegades).

Versió Files Clients diferents
Original (WHERE) 11 9 — només els que van pagar alguna vegada amb targeta
Corregida (ON) 17 15 — els 11 emparellaments + els 6 clients sense cap compra amb targeta, amb NULL

Els 6 clients que apareixen amb NULL a la versió corregida són: en Tiago (8), en Julien (10), en Diego (12), la Núria (13), l'Hugo (14) i la Inés (15). Els tres últims perquè no han comprat mai; els tres primers perquè van comprar, però pagant amb PayPal o transferència.

Conclusió

El LEFT JOIN és, a la pràctica, el JOIN que més problemes de negoci resol:

  • Conserva totes les files de la taula esquerra, casin o no, omplint amb NULL les columnes de la dreta. LEFT JOIN i LEFT OUTER JOIN són el mateix.
  • Els NULL els fabrica el motor en construir el resultat; no eren a les dades. Per això pots detectar l'absència mirant la clau primària de la taula dreta.
  • Has recuperat els tres buits de BotigaVerda: els clients 13, 14 i 15 (23 files en comptes de 20), els productes 13, 19 i 20 (50 files en comptes de 47) i les 10 comandes web (20 files en comptes de 10).
  • El patró anti-join LEFT JOIN ... WHERE dreta.id IS NULL respon a les preguntes amb "sense" i "mai": tres clients que no han comprat, tres productes que ningú no ha venut, onze productes sense ressenyes.
  • Saps que una condició sobre la taula dreta al WHERE degrada el LEFT JOIN a INNER JOIN: 18 files i 15 clients contra 14 files i 12 clients, amb la mateixa pregunta escrita de dues formes. Si vols "tots els X amb els seus Y que compleixin Z", la condició va a l'ON.
  • Un INNER JOIN encadenat després d'un LEFT JOIN anul·la el seu efecte: 23 files es queden en 10. Oberta una branca amb LEFT, continua amb LEFT.
  • El LEFT JOIN no deduplica: continua multiplicant files quan la dreta en té diverses per cada esquerra.

A la lliçó següent, RIGHT JOIN, veurem el simètric exacte del que acabes d'aprendre. Comprovaràs que A RIGHT JOIN B i B LEFT JOIN A retornen exactament el mateix, entendràs per què la majoria de guies d'estil prefereixen el LEFT tot i això, i veuràs per fi Irene Salvador Mira i Daniel Vercher Lluch, els dos empleats que mai no han gestionat una comanda.

Curs de SQL

Mòdul 1: Introducció a SQL

Mòdul 2: Consultes bàsiques de SQL

Mòdul 3: Treballar amb múltiples taules

Mòdul 4: Filtratge avançat de dades

Mòdul 5: Manipulació de dades

Mòdul 6: Funcions avançades de SQL

Mòdul 7: Subconsultes i consultes imbricades

Mòdul 8: Índexs i optimització del rendiment

Mòdul 9: Transaccions i concurrència

Mòdul 10: Temes avançats

Mòdul 11: SQL a la pràctica

Mòdul 12: Projecte final

© Copyright 2026. Tots els drets reservats