Vas tancar el mòdul 2 topant sempre contra el mateix sostre: comandes et diu client_id = 9 i no "Camille Dubois", linies_comanda et diu producte_id = 15 i no "Te verd matcha cerimonial". Aquest sostre s'acaba aquí. En aquesta lliçó aprendràs què és realment un JOIN, no com una fórmula que es copia sinó com una operació que pots reconstruir mentalment pas a pas: un producte cartesià filtrat per una condició. A partir d'aquesta idea, tota la resta —INNER, LEFT, RIGHT, FULL, SELF, CROSS— deixa de ser una llista de noms per memoritzar i es converteix en variacions sobre un mateix mecanisme.

Aquesta és la lliçó paraigua del mòdul: et dona el marc conceptual, la sintaxi i la panoràmica dels cinc tipus. Cadascun d'ells es desenvolupa a fons a les lliçons següents.

Contingut

  1. Per què cal recompondre allò que la normalització va separar
  2. Què és un JOIN: producte cartesià filtrat
  3. La clàusula ON i la condició d'emparellament
  4. Sintaxi moderna enfront de l'antiga de comes
  5. El CROSS JOIN accidental
  6. USING i NATURAL JOIN
  7. Àlies de taula i l'ambigüitat de noms
  8. Panoràmica dels cinc tipus de JOIN
  9. Encadenar tres o més taules
  10. Els JOIN a l'ordre lògic d'execució
  11. Errors habituals i consells
  12. Exercicis
  13. Conclusió

  1. Per què cal recompondre allò que la normalització va separar

A la lliçó 01-05 vam justificar per què el nom de la categoria no es desa dins de cada producte: si "Cosmètica natural" aparegués repetit en quatre files de productes, corregir una errada exigiria quatre UPDATE, i n'hi hauria prou d'oblidar-ne un per tenir dues versions de la mateixa dada convivint a la taula. La normalització resol això desant el nom una sola vegada, a categories, i deixant a productes únicament una referència numèrica: categoria_id.

El preu d'aquesta decisió és exactament el sostre del mòdul 2:

SELECT id, nom, categoria_id
FROM productes
WHERE id <= 3;
id nom categoria_id
1 Oli d'oliva verge extra 500 ml 1
2 Arròs integral ecològic 1 kg 1
3 Mel de tarongina crua 500 g 1

Un 1 no serveix a ningú en un informe. La dada existeix, però està repartida entre dues taules, i cal una operació que les torni a ajuntar en el moment de consultar.

La idea clau del mòdul: normalitzar és separar per emmagatzemar bé; el JOIN és tornar a ajuntar per consultar bé. Són les dues cares de la mateixa moneda, i per això una base de dades ben dissenyada no és una base de dades incòmoda: només exigeix aprendre a recórrer les relacions.

  1. Què és un JOIN: producte cartesià filtrat

La definició formal d'un JOIN cap en una frase:

Un JOIN és el producte cartesià de dues taules, filtrat per una condició.

El producte cartesià emparella cada fila de la primera taula amb cada fila de la segona. Si la primera té 3 files i la segona 2, el resultat en té 3 × 2 = 6.

Vegem-ho amb dos extractes diminuts de BotigaVerda abans de tocar les taules completes. Aquestes són les dues "taules" de treball:

Extracte de categories (2 files):

id nom
4 Begudes
5 Higiene personal

Extracte de productes (3 files):

id nom categoria_id preu
16 Kombutxa de gingebre 750 ml 4 4.95
17 Suc de taronja premsat en fred 1 L 4 5.40
18 Raspall de dents de bambú 5 3.50

Pas 1: el producte cartesià

SELECT p.id  AS producte_id,
       p.nom AS producte,
       p.categoria_id,
       cat.id AS cat_id,
       cat.nom AS categoria
FROM productes AS p
CROSS JOIN categories AS cat
WHERE p.id IN (16, 17, 18)
  AND cat.id IN (4, 5)
ORDER BY p.id, cat.id;
producte_id producte categoria_id cat_id categoria
16 Kombutxa de gingebre 750 ml 4 4 Begudes
16 Kombutxa de gingebre 750 ml 4 5 Higiene personal
17 Suc de taronja premsat en fred 1 L 4 4 Begudes
17 Suc de taronja premsat en fred 1 L 4 5 Higiene personal
18 Raspall de dents de bambú 5 4 Begudes
18 Raspall de dents de bambú 5 5 Higiene personal

6 files. Totes les combinacions possibles. La majoria són brossa: la kombutxa no pertany a "Higiene personal" i el raspall de dents no és una beguda.

Pas 2: quedar-se només amb les combinacions correctes

Les files bones són aquelles en què p.categoria_id coincideix amb cat.id. Mira la taula anterior amb aquest criteri:

producte_id categoria_id cat_id categoria_id = cat_id?
16 4 4 ✅ sí
16 4 5 ❌ no
17 4 4 ✅ sí
17 4 5 ❌ no
18 5 4 ❌ no
18 5 5 ✅ sí

Sobreviuen tres files, una per producte. Això és exactament el que fa un JOIN:

SELECT p.id  AS producte_id,
       p.nom AS producte,
       cat.nom AS categoria
FROM productes AS p
JOIN categories AS cat ON p.categoria_id = cat.id
WHERE p.id IN (16, 17, 18)
ORDER BY p.id;
producte_id producte categoria
16 Kombutxa de gingebre 750 ml Begudes
17 Suc de taronja premsat en fred 1 L Begudes
18 Raspall de dents de bambú Higiene personal

Representat com a flux de files:

flowchart LR
    A["productes<br/>3 files"] --> C["producte cartesià<br/>3 × 2 = 6 files"]
    B["categories<br/>2 files"] --> C
    C --> D["filtre ON<br/>p.categoria_id = cat.id"]
    D --> E["resultat<br/>3 files"]

Un matís important sobre el rendiment. Que el JOIN es defineixi així no significa que el motor l'executi així. PostgreSQL no materialitza 300 files per llençar-ne 280: fa servir algorismes com hash join, merge join o nested loop que van directament a les parelles que casen, normalment recolzant-se en índexs. És la mateixa distinció entre ordre lògic i pla físic que vas veure a 02-01, i l'estudiaràs amb EXPLAIN al mòdul 8. Per raonar sobre què retorna una consulta, el model mental "cartesià + filtre" és sempre correcte.

La consulta completa sobre les 20 files

Tret el WHERE de retall, el JOIN respon per fi a "de quina categoria és cada producte?":

SELECT p.id,
       p.nom   AS producte,
       cat.nom AS categoria
FROM productes AS p
JOIN categories AS cat ON p.categoria_id = cat.id
ORDER BY p.id;
id producte categoria
1 Oli d'oliva verge extra 500 ml Alimentació
2 Arròs integral ecològic 1 kg Alimentació
3 Mel de tarongina crua 500 g Alimentació
4 Pasta d'espelta 500 g Alimentació
5 Tomàquet triturat ecològic 400 g Alimentació
6 Crema facial d'àloe vera 50 ml Cosmètica natural
7 Xampú sòlid de romaní 80 g Cosmètica natural
8 Oli corporal d'ametlles 200 ml Cosmètica natural
9 Bàlsam labial de calèndula 15 ml Cosmètica natural
10 Detergent ecològic concentrat 1 L Llar sostenible
11 Fregall vegetal de lufa (pack 3) Llar sostenible
12 Bosses reutilitzables de cotó (pack 5) Llar sostenible
13 Espelmes de cera de soja (pack 2) Llar sostenible
14 Infusió de camamilla ecològica 20 u Begudes
15 Te verd matcha cerimonial 30 g Begudes
16 Kombutxa de gingebre 750 ml Begudes
17 Suc de taronja premsat en fred 1 L Begudes
18 Raspall de dents de bambú Higiene personal
19 Desodorant natural en barra 50 g Higiene personal
20 Càpsules d'espirulina 120 u Complements

20 files, les mateixes que té productes. Aquest recompte no és casualitat i mereix una regla que faràs servir constantment:

Quan uneixes una taula amb N files contra una altra per una clau forana obligatòria que apunta a una clau primària, el resultat té exactament N files: cada fila troba una parella i només una. Si el recompte canvia, alguna cosa no és com et pensaves.

  1. La clàusula ON i la condició d'emparellament

ON conté la condició d'emparellament: la regla que decideix quina fila de l'esquerra va amb quina fila de la dreta.

FROM productes AS p
JOIN categories AS cat ON p.categoria_id = cat.id

En el 95 % dels casos que escriuràs a la teva vida, aquesta condició té exactament aquesta forma:

ON <taula_filla>.<clau_forana> = <taula_pare>.<clau_primària>

És a dir: FK = PK. La raó és evident si recordes 01-05: la clau forana existeix precisament per assenyalar una fila concreta de l'altra taula, així que el camí natural entre dues taules és el que dibuixa la mateixa FK.

Aquestes són les condicions d'emparellament canòniques de BotigaVerda. Consulta-les cada vegada que dubtis:

Des de Cap a Condició ON
productes categories p.categoria_id = cat.id
productes proveidors p.proveidor_id = pr.id
comandes clients co.client_id = c.id
comandes empleats co.empleat_id = e.id
linies_comanda comandes lc.comanda_id = co.id
linies_comanda productes lc.producte_id = p.id
ressenyes productes r.producte_id = p.id
ressenyes clients r.client_id = c.id
devolucions comandes d.comanda_id = co.id
empleats empleats (cap) e.cap_id = cap.id
clients clients (referidor) c.referit_per_id = ref.id

Encara que la igualtat FK = PK sigui l'habitual, ON admet qualsevol expressió booleana, igual que WHERE:

-- Condició composta: diverses igualtats unides per AND
ON r.producte_id = lc.producte_id AND r.data >= co.data_comanda

-- Condició de desigualtat (non-equi join): ho veuràs a 03-06
ON p1.categoria_id = p2.categoria_id AND p1.id < p2.id

Quan la condició no és una igualtat es parla de non-equi join. Són minoria, però existeixen: rangs de dates, escales de preus, comparacions entre files de la mateixa taula. El cas de p1.id < p2.id el faràs servir a la lliçó 03-06 per generar parelles de productes sense repeticions.

  1. Sintaxi moderna enfront de l'antiga de comes

Abans de l'estàndard SQL-92 no existia la paraula JOIN. Les taules es llistaven separades per comes al FROM i la condició d'emparellament s'escrivia al WHERE:

-- ⚠️ Sintaxi antiga (SQL-89). Funciona, però està desaconsellada.
SELECT co.id,
       co.data_comanda,
       c.nom,
       c.cognoms
FROM comandes AS co, clients AS c
WHERE co.client_id = c.id
  AND co.estat = 'pendent';
-- ✅ Sintaxi moderna (SQL-92 endavant). La del curs.
SELECT co.id,
       co.data_comanda,
       c.nom,
       c.cognoms
FROM comandes AS co
JOIN clients AS c ON co.client_id = c.id
WHERE co.estat = 'pendent';
id data_comanda nom cognoms
20 2026-02-21 Camille Dubois

Totes dues retornen el mateix i PostgreSQL genera el mateix pla per a les dues. Però l'antiga té quatre problemes seriosos:

Problema Explicació
Barreja dues coses diferents El WHERE acaba contenint condicions d'emparellament (co.client_id = c.id) i condicions de filtratge (co.estat = 'pendent') remenades. Amb sis taules i dotze condicions, distingir-les és un exercici d'arqueologia
És fàcil oblidar una condició I l'oblit no dona error: produeix un producte cartesià silenciós (secció 5)
No permet LEFT/RIGHT/FULL Els JOIN externs, que són la meitat d'aquest mòdul, no tenen expressió en la sintaxi de comes. Oracle tenia (+) i SQL Server *= com a extensions propietàries, totes dues avui obsoletes
Trenca la simetria del codi Amb la sintaxi moderna, cada taula afegida és una línia JOIN ... ON ... autocontinguda. Afegir o treure una taula és una edició local

Regla del curs: sempre JOIN ... ON. El FROM descriu com es relacionen les taules; el WHERE descriu quines files ens interessen. Mai no es barregen. L'única excepció admesa és el CROSS JOIN deliberat, que veurem a 03-06.

  1. El CROSS JOIN accidental

Aquest és el motiu pràctic pel qual la sintaxi de comes es va abandonar. Oblida la condició del WHERE:

-- ⚠️ INCORRECTA: falta la condició d'emparellament
SELECT p.nom AS producte,
       c.nom AS client
FROM productes AS p, clients AS c;
(300 files)

300 files = 20 productes × 15 clients. I el pitjor: no hi ha cap error. La consulta s'executa, retorna dades amb aspecte plausible i, si la fiques en un informe, aquest informe estarà malament sense que res ho delati.

El mateix oblit amb la sintaxi moderna és impossible de cometre sense adonar-se'n:

-- ⚠️ INCORRECTA, però aquesta vegada el motor et para
SELECT p.nom, c.nom
FROM productes AS p
JOIN clients AS c;
ERROR:  syntax error at or near ";"
LINE 3: JOIN clients AS c;
                         ^

JOIN exigeix un ON (o un USING). Si de debò vols el producte cartesià, l'has de demanar explícitament amb CROSS JOIN, que és una declaració d'intencions que ningú no escriu per accident.

La mida de l'accident creix molt de pressa:

Taula A Taula B Files del cartesià
categories (6) proveidors (5) 30
productes (20) clients (15) 300
linies_comanda (47) comandes (20) 940
100 000 100 000 10 000 000 000

Aquesta última fila és la raó per la qual un JOIN mal escrit pot tombar un servidor. A BotigaVerda només es tradueix en un resultat absurd; en producció, en una trucada a les tres de la matinada.

Símptoma inconfusible: si una consulta retorna moltíssimes més files de les que esperaves i les dades semblen repetir-se en bucle, compta les teves condicions ON. Amb N taules calen N-1 condicions d'emparellament. Tres taules, dos ON. Cinc taules, quatre ON.

  1. USING i NATURAL JOIN

SQL ofereix dues dreceres per escriure menys. Una és útil amb reserves; l'altra és un parany.

USING: quan les columnes es diuen igual

Si la columna d'emparellament té el mateix nom a totes dues taules, USING (columna) substitueix l'ON:

-- Equivalents quan totes dues taules tenen una columna anomenada producte_id
JOIN ressenyes AS r ON lc.producte_id = r.producte_id
JOIN ressenyes AS r USING (producte_id)

USING té una propietat que ON no té: fusiona la columna comuna en una de sola, en lloc de retornar-la dues vegades. Per això la pots escriure sense qualificar:

SELECT producte_id,
       lc.id AS linia_id,
       lc.comanda_id,
       r.id AS ressenya_id,
       r.puntuacio
FROM linies_comanda AS lc
JOIN ressenyes AS r USING (producte_id)
WHERE producte_id = 15
ORDER BY lc.id;
producte_id linia_id comanda_id ressenya_id puntuacio
15 9 4 5 5
15 28 12 5 5
15 42 17 5 5

A BotigaVerda USING és gairebé inservible, i no per casualitat: l'esquema segueix la convenció que la clau primària es diu id i la forana <taula>_id. Com que productes.id i linies_comanda.producte_id no es diuen igual, USING no s'hi aplica. Només funciona entre dues taules filles que comparteixen el nom de la FK, com l'exemple de dalt.

ON USING
Noms de columna Poden ser diferents Han de ser idèntics
Columna comuna al resultat Apareix dues vegades Apareix una vegada, fusionada
Condicions compostes o desigualtats No: només llistes de columnes amb igualtat
Ús a BotigaVerda Sempre Excepcional

NATURAL JOIN: la drecera perillosa

NATURAL JOIN va un pas més enllà: emparella automàticament per totes les columnes que es diguin igual a totes dues taules, sense que tu diguis quines.

-- ⚠️ INCORRECTA a la pràctica: no fa el que sembla
SELECT COUNT(*) AS files
FROM productes NATURAL JOIN categories;
files
0

Zero files. (COUNT(*) simplement compta les files del resultat; és una funció d'agregació i s'estudia a 04-04. Aquí la fem servir com a instrument de mesura, i és l'única vegada que apareixerà en aquest mòdul.)

Per què zero? Perquè productes i categories comparteixen dos noms de columna: id i nom. NATURAL JOIN construeix pel seu compte la condició:

ON productes.id = categories.id AND productes.nom = categories.nom

És a dir, exigeix que el producte i la categoria tinguin el mateix identificador i el mateix nom. Cap parella no ho compleix. La consulta no falla, no avisa: retorna un resultat buit perfectament educat.

I hi ha una cosa pitjor que un resultat buit: un resultat que canvia sol. Si demà algú afegeix una columna actiu a categories, aquell NATURAL JOIN començarà a emparellar també per actiu i retornarà una altra cosa, sense que ningú hagi tocat la consulta.

Regla del curs: NATURAL JOIN està prohibit. És una drecera que estalvia vint caràcters a canvi que el significat de la teva consulta depengui dels noms de columna que algú triï en el futur. En aquest esquema, a més, nom existeix a categories, proveidors, productes, clients i empleats: la mina està sembrada.

  1. Àlies de taula i l'ambigüitat de noms

Tan bon punt hi ha dues taules en joc, els noms de columna es poden repetir. I si es repeteixen, el motor no ho endevina:

-- ⚠️ INCORRECTA
SELECT id, nom
FROM productes
JOIN categories ON productes.categoria_id = categories.id;
ERROR:  column reference "id" is ambiguous
LINE 1: SELECT id, nom
               ^

Tant productes com categories tenen id i nom. PostgreSQL no tria per tu: t'obliga a qualificar la columna amb el nom (o l'àlies) de la seva taula.

Ho podries escriure tot amb el nom complet de la taula:

SELECT productes.id, productes.nom, categories.nom
FROM productes
JOIN categories ON productes.categoria_id = categories.id;

Funciona, però és insuportablement verbós tan bon punt hi ha quatre taules. Els àlies de taula ho resolen:

-- ✅ CORRECTA
SELECT p.id,
       p.nom   AS producte,
       cat.nom AS categoria
FROM productes AS p
JOIN categories AS cat ON p.categoria_id = cat.id
ORDER BY p.id
LIMIT 3;
id producte categoria
1 Oli d'oliva verge extra 500 ml Alimentació
2 Arròs integral ecològic 1 kg Alimentació
3 Mel de tarongina crua 500 g Alimentació

Fixa't que aquí calen dos tipus d'àlies diferents, i convé no confondre'ls:

Tipus On va Per a què Exemple
Àlies de taula Al FROM/JOIN Qualificar columnes sense escriure el nom sencer productes AS p
Àlies de columna Al SELECT Posar nom a la columna del resultat (02-02) p.nom AS producte

Sense l'àlies de columna, el resultat tindria dues columnes anomenades nom, i ni tu ni la teva aplicació sabríeu quina és quina. És exactament el problema que anticipàvem a 02-01 quan parlàvem de SELECT *.

Àlies de taula del curs. Perquè totes les consultes del temari es llegeixin igual, fixem aquestes abreviatures i les farem servir sempre:

Taula Àlies Taula Àlies
productes p comandes co
categories cat linies_comanda lc
proveidors pr ressenyes r
clients c devolucions d
empleats e

I quan la mateixa taula aparegui dues vegades (els self join de 03-06), els àlies deixen de ser una comoditat per ser obligatoris, i farem servir noms significatius: empleats AS e i empleats AS cap, clients AS c i clients AS ref.

Dos detalls de sintaxi:

  • L'AS dels àlies de taula és opcional: FROM productes p és idèntic a FROM productes AS p. En aquest curs l'escrivim sempre, per coherència amb els àlies de columna.
  • Un cop definit l'àlies, el nom original deixa de ser utilitzable. Si escrius FROM productes AS p, no pots referir-te a productes.preu: donarà missing FROM-clause entry for table "productes".

  1. Panoràmica dels cinc tipus de JOIN

Tots els JOIN comparteixen el mecanisme de la secció 2. El que canvia és què es fa amb les files que no troben parella.

flowchart TD
    Q{"Què faig amb les files<br/>que no troben parella?"}
    Q -->|"Descartar-les totes"| I["INNER JOIN<br/>03-02"]
    Q -->|"Conservar les de l'esquerra"| L["LEFT JOIN<br/>03-03"]
    Q -->|"Conservar les de la dreta"| R["RIGHT JOIN<br/>03-04"]
    Q -->|"Conservar les dels dos costats"| F["FULL OUTER JOIN<br/>03-05"]
    Q -->|"No hi ha condició:<br/>totes amb totes"| C["CROSS JOIN<br/>03-06"]

En forma de taula, amb la pregunta típica de BotigaVerda que resol cadascun:

Tipus Què retorna Quan fer-lo servir Pregunta típica Lliçó
INNER JOIN Només les files que casen a banda i banda Quan tots dos costats són obligatoris perquè la fila tingui sentit "De quina categoria és cada producte?" 03-02
LEFT JOIN Totes les de l'esquerra + les que casin de la dreta (NULL si no casen) Quan la taula esquerra és la protagonista i la dreta és opcional "Què ha demanat cada client, inclosos els que no han demanat res?" 03-03
RIGHT JOIN Totes les de la dreta + les que casin de l'esquerra El mateix, amb els papers invertits "Quines comandes va gestionar cada empleat, inclosos els que no en van gestionar cap?" 03-04
FULL OUTER JOIN Totes les dels dos costats Conciliar dues fonts que poden tenir elements que l'altra no té "Què hi ha al catàleg que no és a vendes, i què hi ha a vendes que no és al catàleg?" 03-05
CROSS JOIN Totes les combinacions, sense condició Generar combinacions deliberadament (calendaris, matrius) "Dona'm totes les parelles categoria × mes, encara que no hi hagués vendes" 03-06

A aquests cinc s'hi afegeix el SELF JOIN, que no és un sisè tipus sinó una tècnica: fer servir qualsevol dels anteriors per unir una taula amb si mateixa, i així recórrer les relacions reflexives d'empleats.cap_id i clients.referit_per_id. També és 03-06.

Un apunt de vocabulari que veuràs a la documentació: INNER JOIN és el join intern; LEFT, RIGHT i FULL són els joins externs (outer joins), perquè conserven files que queden "fora" de l'emparellament. D'aquí que el seu nom complet sigui LEFT OUTER JOIN, RIGHT OUTER JOIN i FULL OUTER JOIN; la paraula OUTER és opcional en tots tres.

  1. Encadenar tres o més taules

Les preguntes de negoci poques vegades es resolen amb dues taules. Encadenar és simplement afegir una línia JOIN ... ON ... per cada taula nova, i funciona perquè el resultat d'un JOIN és al seu torn una taula que es pot tornar a unir.

flowchart LR
    A["linies_comanda"] -->|"lc.comanda_id = co.id"| B["comandes"]
    B -->|"co.client_id = c.id"| C["clients"]

Cadena 1: de la línia al client

"Qui va comprar cada línia de comanda?" El nom del client no és a linies_comanda ni és assolible en un salt: cal passar per comandes.

SELECT lc.id AS linia_id,
       co.id AS comanda_id,
       c.nom || ' ' || c.cognoms AS client,
       lc.producte_id,
       lc.quantitat
FROM linies_comanda AS lc
JOIN comandes AS co ON lc.comanda_id = co.id
JOIN clients  AS c  ON co.client_id = c.id
ORDER BY lc.id
LIMIT 10;
linia_id comanda_id client producte_id quantitat
1 1 Lucía Martínez Soler 1 2
2 1 Lucía Martínez Soler 2 3
3 1 Lucía Martínez Soler 14 2
4 2 Carlos Ferrer Ibáñez 6 1
5 2 Carlos Ferrer Ibáñez 9 2
6 3 Marta Sanchis Gil 5 6
7 3 Marta Sanchis Gil 4 4
8 3 Marta Sanchis Gil 2 2
9 4 Javier Ortega Ruiz 15 1
10 4 Javier Ortega Ruiz 3 1

(10 primeres de 47 files.)

Tres taules, dues condicions ON, i el recompte continua sent 47: el mateix que té linies_comanda. Fixa't que el nom del client es repeteix a les tres primeres files, perquè la comanda 1 té tres línies. Això no és un error: és la conseqüència natural d'unir pel costat "molts" d'una relació 1:N, i a 03-02 veuràs per què és la causa número u de les sumes inflades del mòdul 4.

Cadena 2: de la línia a la categoria

flowchart LR
    A["linies_comanda"] -->|"lc.producte_id = p.id"| B["productes"]
    B -->|"p.categoria_id = cat.id"| C["categories"]
SELECT lc.id AS linia_id,
       p.nom   AS producte,
       cat.nom AS categoria,
       lc.quantitat,
       ROUND(lc.quantitat * lc.preu_unitari * (1 - lc.descompte), 2) AS import
FROM linies_comanda AS lc
JOIN productes  AS p   ON lc.producte_id = p.id
JOIN categories AS cat ON p.categoria_id = cat.id
ORDER BY lc.id
LIMIT 10;
linia_id producte categoria quantitat import
1 Oli d'oliva verge extra 500 ml Alimentació 2 23.90
2 Arròs integral ecològic 1 kg Alimentació 3 11.70
3 Infusió de camamilla ecològica 20 u Begudes 2 6.50
4 Crema facial d'àloe vera 50 ml Cosmètica natural 1 17.50
5 Bàlsam labial de calèndula 15 ml Cosmètica natural 2 9.20
6 Tomàquet triturat ecològic 400 g Alimentació 6 10.53
7 Pasta d'espelta 500 g Alimentació 4 11.20
8 Arròs integral ecològic 1 kg Alimentació 2 7.80
9 Te verd matcha cerimonial 30 g Begudes 1 22.00
10 Mel de tarongina crua 500 g Alimentació 1 9.75

(10 primeres de 47 files.)

Ja hi és, per fi, el detall de vendes llegible: què es va vendre, de quina categoria i per quant. Al mòdul 4 sumarem aquests imports per categoria i respondrem a "quina categoria factura més?"; de moment ens quedem a les files de detall, que és el que aquest mòdul sap produir.

Observa com es calcula l'import seguint la convenció de 02-02: s'opera amb precisió completa dins del ROUND i s'arrodoneix només en presentar. La línia 6 ho il·lustra: 6 * 1.95 * 0.90 = 10.53.

Com es construeix una cadena, en tres passos. Aquest mètode t'evitarà gairebé tots els errors:

  1. Localitza el punt de partida: la taula que conté el nivell de detall que vols (una fila per línia de comanda → linies_comanda).
  2. Traça el camí al diagrama ER de 01-06 fins a cada dada que necessitis, seguint les fletxes de les claus foranes.
  3. Escriu un JOIN ... ON per cada salt, en l'ordre del camí, i comprova el recompte de files al final.

  1. Els JOIN a l'ordre lògic d'execució

Aquí arriba l'ampliació del diagrama que anem arrossegant des de 02-01, i és la peça més important de la lliçó per al que ve després.

La pregunta que resol és: en quin moment es resolen els JOIN? Resposta: dins del pas FROM, abans de WHERE.

flowchart TD
    subgraph FROM["1 · FROM — es construeix el conjunt de partida"]
        direction LR
        A1["1a · taules base"] --> A2["1b · producte cartesià<br/>de cada parella"]
        A2 --> A3["1c · condició ON<br/>filtra els emparellaments"]
        A3 --> A4["1d · s'afegeixen les files<br/>sense parella (LEFT/RIGHT/FULL)"]
    end
    FROM --> B["2 · WHERE<br/>filtra files del resultat ja unit"]
    B --> C["3 · SELECT<br/>projecta i calcula<br/>neixen els àlies"]
    C --> D["3b · DISTINCT<br/>elimina duplicats"]
    D --> E["4 · ORDER BY<br/>ordena el resultat"]
    E --> F["5 · LIMIT / OFFSET<br/>retalla"]

Llegeix-ho a poc a poc, perquè d'aquí surten tres conseqüències que governen tot el mòdul:

1. ON i WHERE s'executen en moments diferents. ON actua mentre es construeix l'emparellament; WHERE actua després, sobre la taula ja combinada.

2. En un INNER JOIN aquesta diferència no es nota. Posar co.estat = 'lliurat' a l'ON o al WHERE dona exactament el mateix resultat, perquè en un INNER JOIN no hi ha pas 1d: les files sense parella es descarten igualment. Ho comprovaràs a 03-02.

3. En un LEFT JOIN la diferència és enorme. El pas 1d reintrodueix les files sense parella després d'aplicar l'ON, però abans d'aplicar el WHERE. Resultat:

On poses la condició Què passa en un LEFT JOIN
A l'ON Restringeix què s'emparella. Les files de l'esquerra sense parella continuen apareixent, amb NULL a la dreta
Al WHERE Filtra el resultat final. Les files amb NULL a la dreta no compleixen la condició i desapareixen: el LEFT JOIN es degrada silenciosament a INNER JOIN

És l'error més freqüent i més difícil de detectar de tot l'SQL intermedi, i la lliçó 03-03 el demostra amb la mateixa consulta escrita de les dues formes i els seus dos resultats diferents. Tingues aquest diagrama a mà quan hi arribis.

Una nota final sobre l'ordre dins del mateix FROM: quan encadenes diversos JOIN, es resolen d'esquerra a dreta. A JOIN B JOIN C significa (A JOIN B) JOIN C: primer s'uneix A amb B, i el resultat s'uneix amb C. Amb INNER JOIN purs l'ordre és indiferent (03-02); amb LEFT JOIN encadenats, no ho és en absolut (03-03).

Errors habituals i consells

  • Oblidar la condició ON fent servir la sintaxi de comes. Produeix un producte cartesià silenciós: moltes files, cap error. Amb N taules necessites N-1 condicions d'emparellament.
  • column reference "id" is ambiguous. Dues taules del FROM tenen una columna amb aquest nom. Qualifica-la: p.id, cat.id. Passa sempre amb id i amb nom en aquest esquema.
  • missing FROM-clause entry for table "productes". Has definit l'àlies p i després has escrit productes.preu. Un cop hi ha àlies, el nom original ja no existeix per a aquella consulta.
  • Fer servir NATURAL JOIN "perquè és més curt". Emparella per totes les columnes homònimes, incloses les que algú afegeixi demà. A productes NATURAL JOIN categories retorna 0 files per culpa de nom.
  • Emparellar per la columna equivocada. ON lc.producte_id = co.id compila perfectament i retorna brossa: estàs comparant un identificador de producte amb un de comanda. Comprova sempre que tots dos costats de la igualtat parlen de la mateixa entitat.
  • Suposar que un JOIN mai no canvia el nombre de files. Només es conserva quan uneixes per FK obligatòria contra PK. Si la FK admet NULL, perds files (03-03); si el costat dret té diverses files per cada esquerra, les multipliques (03-02).
  • Barrejar sintaxi antiga i moderna a la mateixa consulta. FROM a, b JOIN c ON ... és legal i és un camp de mines de precedència. No ho facis.
  • Consell: escriu el JOIN abans que el SELECT. Construeix primer el FROM amb les seves cadenes, executa amb SELECT * i LIMIT 5 per veure la forma del resultat, i només llavors tria les columnes.
  • Consell: compta les files després de cada JOIN que afegeixis. Si afegeixes una taula i el recompte es dispara, aquella taula té diverses files per cada fila anterior. Si cau a zero, la teva condició ON no casa res.
  • Consell: tingues obert el diagrama ER de 01-06. Els JOIN són camins per aquell diagrama. Ningú no els memoritza; es llegeixen.

Exercicis

Exercici 1

Compres necessita saber quina part del catàleg depèn de proveïdors estrangers. Escriu una consulta que retorni, només per als productes el proveïdor dels quals no és d'Espanya: l'id i el nom del producte, el seu preu, el nom del proveïdor i el seu país. Ordena per país i, dins de cada país, per id de producte.

Després respon: per què la condició sobre el país va al WHERE i no a l'ON?

Exercici 2

T'arriba aquesta consulta escrita per un company:

SELECT co.id, co.data_comanda, c.nom, c.cognoms, c.ciutat
FROM comandes co, clients c
WHERE co.client_id = c.id
  AND c.pais = 'França'
  AND co.estat = 'lliurat';
  1. Reescriu-la amb sintaxi moderna JOIN ... ON, separant clarament emparellament i filtratge.
  2. Què passaria exactament si algú esborrés per accident la línia co.client_id = c.id a la versió original? Calcula quantes files retornaria.

Exercici 3

Sense executar res, prediu el nombre de files que retorna cadascuna d'aquestes consultes i justifica cada predicció en una frase. Després executa-les i comprova-ho.

-- a)
SELECT * FROM productes AS p
JOIN categories AS cat ON p.categoria_id = cat.id;

-- b)
SELECT * FROM productes AS p, categories AS cat;

-- c)
SELECT * FROM linies_comanda AS lc
JOIN comandes AS co ON lc.comanda_id = co.id;

-- d)
SELECT * FROM comandes AS co
JOIN empleats AS e ON co.empleat_id = e.id;

-- e)
SELECT * FROM ressenyes AS r
JOIN clients AS c ON r.client_id = c.id;

Solucions

Solució 1

SELECT p.id,
       p.nom  AS producte,
       p.preu,
       pr.nom AS proveidor,
       pr.pais
FROM productes AS p
JOIN proveidors AS pr ON p.proveidor_id = pr.id
WHERE pr.pais <> 'Espanya'
ORDER BY pr.pais, p.id;
id producte preu proveidor pais
10 Detergent ecològic concentrat 1 L 11.20 EcoNordic Supplies Alemanya
13 Espelmes de cera de soja (pack 2) 13.75 EcoNordic Supplies Alemanya
18 Raspall de dents de bambú 3.50 EcoNordic Supplies Alemanya
20 Càpsules d'espirulina 120 u 16.40 EcoNordic Supplies Alemanya
6 Crema facial d'àloe vera 50 ml 18.90 Maison Nature França
7 Xampú sòlid de romaní 80 g 8.40 Maison Nature França
9 Bàlsam labial de calèndula 15 ml 4.60 Maison Nature França
19 Desodorant natural en barra 50 g 7.80 Maison Nature França
8 Oli corporal d'ametlles 200 ml 14.25 Verde Atlántico Portugal
11 Fregall vegetal de lufa (pack 3) 5.50 Verde Atlántico Portugal
12 Bosses reutilitzables de cotó (pack 5) 9.90 Verde Atlántico Portugal
15 Te verd matcha cerimonial 30 g 22.00 Verde Atlántico Portugal

12 files de les 20 del catàleg: 8 productes vénen dels dos proveïdors espanyols (Huerta del Turia i BioSierra Ibérica) i els altres 12 d'Alemanya, França i Portugal.

Per què la condició va al WHERE: pr.pais <> 'Espanya' no diu com s'emparellen les taules —això ja ho diu p.proveidor_id = pr.id—, diu quines files del resultat ens interessen. És la separació de responsabilitats de la secció 4: ON per emparellar, WHERE per filtrar.

Dit això, en aquest cas concret posar-la a l'ON donaria el mateix resultat, perquè és un INNER JOIN i els passos 1c i 2 de l'ordre lògic es comporten igual quan no hi ha files òrfenes per reintroduir. La distinció es torna crítica al LEFT JOIN de 03-03, i per això convé agafar el costum correcte des d'ara.

Solució 2

1. Reescriptura moderna:

SELECT co.id,
       co.data_comanda,
       c.nom,
       c.cognoms,
       c.ciutat
FROM comandes AS co
JOIN clients AS c ON co.client_id = c.id
WHERE c.pais = 'França'
  AND co.estat = 'lliurat';
id data_comanda nom cognoms ciutat
10 2025-08-03 Camille Dubois Lió
12 2025-10-01 Julien Moreau París

Les dues comandes lliurades de clients francesos. La reescriptura deixa a la vista el que la versió antiga amagava: una sola condició d'emparellament i dues de filtratge.

2. Si s'esborra co.client_id = c.id:

La consulta passa a ser un producte cartesià filtrat només per país i estat. El càlcul:

  • Clients de França: 2 (Camille Dubois i Julien Moreau).
  • Comandes amb estat lliurat: 14.
  • Files resultants: 2 × 14 = 28.

I serien 28 files falses: cada comanda lliurada apareixeria associada als dos clients francesos, incloses les comandes que en realitat són de la Lucía, de la Sofia o d'en Tiago. Cap error, cap avís, un informe completament inventat. És exactament el perill de la secció 5.

Solució 3

# Files Justificació
a) 20 Cada producte té una categoria_id que apunta a una categoria existent: una parella per producte. El recompte de la taula de partida es conserva
b) 120 Producte cartesià: 20 productes × 6 categories. No hi ha cap condició que filtri
c) 47 Cada línia pertany a una comanda i comanda_id és NOT NULL: una parella per línia. El recompte de linies_comanda es conserva
d) 10 Aquí es perden files. De les 20 comandes, 10 tenen empleat_id IS NULL (les comandes web). Un NULL no és igual a res —ni tan sols a un altre NULL, com vas veure a 02-03—, així que aquelles 10 files no troben parella i l'INNER JOIN les descarta
e) 12 Cada ressenya té un client_id obligatori i vàlid: una parella per ressenya

El cas d) és el més instructiu dels cinc i el motiu que existeixin les lliçons 03-03 i 03-04: si la direcció et demana "el llistat de comandes amb el seu comercial" i lliures 10 files de 20, has perdut la meitat del negoci sense assabentar-te'n.

Conclusió

Ja tens el marc conceptual complet del mòdul:

  • La normalització separa per emmagatzemar sense redundància; el JOIN recompon per consultar. Són complementaris, no contradictoris.
  • Un JOIN és un producte cartesià filtrat per una condició. Aquest model mental —combinar-ho tot amb tot i quedar-se amb el que casa— explica el comportament de tots els tipus de JOIN, encara que el motor l'executi de manera molt més eficient.
  • La condició va a l'ON i gairebé sempre té la forma FK = PK. Tens la taula de les onze condicions canòniques de BotigaVerda per consultar-la sempre que dubtis.
  • La sintaxi moderna JOIN ... ON substitueix l'antiga de comes: separa emparellament de filtratge, impedeix el CROSS JOIN accidental i és l'única que admet JOIN externs.
  • USING només serveix si les columnes es diuen igual (rar en aquest esquema) i NATURAL JOIN està prohibit: a productes NATURAL JOIN categories retorna 0 files per culpa de la columna nom.
  • Els àlies de taula són obligatoris a la pràctica: sense ells arriben els column reference "id" is ambiguous. Tens fixats els del curs: p, cat, pr, c, e, co, lc, r, d.
  • Coneixes la panoràmica dels cinc tipus i saps que l'única diferència entre ells és què es fa amb les files que no troben parella.
  • Saps encadenar tres o més taules seguint el diagrama ER, amb N-1 condicions ON per a N taules, i tens els dos camins que faràs servir tot el curs: linies_comanda → comandes → clients i linies_comanda → productes → categories.
  • I saps que, en l'ordre lògic, els JOIN es resolen dins del pas FROM, abans del WHERE: d'aquí en sortirà la diferència crítica entre posar una condició a ON o a WHERE.

A la lliçó següent, INNER JOIN, baixarem al detall del tipus que acabes de fer servir sense anomenar-lo: quines files conserva, quines perd i per què. Veuràs desaparèixer els clients 13, 14 i 15 en unir clients amb comandes, i les 10 comandes web en unir comandes amb empleats. Aquestes desaparicions, lluny de ser una fallada, són la definició mateixa de l'INNER JOIN, i entendre-les és el que fa que el LEFT JOIN de 03-03 tingui sentit.

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