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
- Per què cal recompondre allò que la normalització va separar
- Què és un
JOIN: producte cartesià filtrat - La clàusula
ONi la condició d'emparellament - Sintaxi moderna enfront de l'antiga de comes
- El
CROSS JOINaccidental USINGiNATURAL JOIN- Àlies de taula i l'ambigüitat de noms
- Panoràmica dels cinc tipus de
JOIN - Encadenar tres o més taules
- Els
JOINa l'ordre lògic d'execució - Errors habituals i consells
- Exercicis
- Conclusió
- 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:
| 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.
- Què és un
JOIN: producte cartesià filtrat
JOIN: producte cartesià filtratLa 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.
- La clàusula
ON i la condició d'emparellament
ON i la condició d'emparellamentON conté la condició d'emparellament: la regla que decideix quina fila de l'esquerra va amb quina fila de la dreta.
En el 95 % dels casos que escriuràs a la teva vida, aquesta condició té exactament aquesta forma:
É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.idQuan 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.
- 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. ElFROMdescriu com es relacionen les taules; elWHEREdescriu quines files ens interessen. Mai no es barregen. L'única excepció admesa és elCROSS JOINdeliberat, que veurem a 03-06.
- El
CROSS JOIN accidental
CROSS JOIN accidentalAquest é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 = 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;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, dosON. Cinc taules, quatreON.
USING i NATURAL JOIN
USING i NATURAL JOINSQL 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 | Sí | 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ó:
É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 JOINestà 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,nomexisteix acategories,proveidors,productes,clientsiempleats: la mina està sembrada.
- À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;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'
ASdels àlies de taula és opcional:FROM productes pés idèntic aFROM 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 aproductes.preu: donaràmissing FROM-clause entry for table "productes".
- Panoràmica dels cinc tipus de
JOIN
JOINTots 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.
- 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:
- Localitza el punt de partida: la taula que conté el nivell de detall que vols (una fila per línia de comanda →
linies_comanda). - Traça el camí al diagrama ER de 01-06 fins a cada dada que necessitis, seguint les fletxes de les claus foranes.
- Escriu un
JOIN ... ONper cada salt, en l'ordre del camí, i comprova el recompte de files al final.
- Els
JOIN a l'ordre lògic d'execució
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ó
ONfent 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 delFROMtenen una columna amb aquest nom. Qualifica-la:p.id,cat.id. Passa sempre ambidi ambnomen aquest esquema.missing FROM-clause entry for table "productes". Has definit l'àliespi després has escritproductes.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à. Aproductes NATURAL JOIN categoriesretorna 0 files per culpa denom. - Emparellar per la columna equivocada.
ON lc.producte_id = co.idcompila 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
JOINmai no canvia el nombre de files. Només es conserva quan uneixes per FK obligatòria contra PK. Si la FK admetNULL, 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
JOINabans que elSELECT. Construeix primer elFROMamb les seves cadenes, executa ambSELECT *iLIMIT 5per veure la forma del resultat, i només llavors tria les columnes. - Consell: compta les files després de cada
JOINque 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óONno casa res. - Consell: tingues obert el diagrama ER de 01-06. Els
JOINsó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';- Reescriu-la amb sintaxi moderna
JOIN ... ON, separant clarament emparellament i filtratge. - Què passaria exactament si algú esborrés per accident la línia
co.client_id = c.ida 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
JOINrecompon 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 deJOIN, encara que el motor l'executi de manera molt més eficient. - La condició va a l'
ONi 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 ... ONsubstitueix l'antiga de comes: separa emparellament de filtratge, impedeix elCROSS JOINaccidental i és l'única que admetJOINexterns. USINGnomés serveix si les columnes es diuen igual (rar en aquest esquema) iNATURAL JOINestà prohibit: aproductes NATURAL JOIN categoriesretorna 0 files per culpa de la columnanom.- 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
ONper a N taules, i tens els dos camins que faràs servir tot el curs:linies_comanda → comandes → clientsilinies_comanda → productes → categories. - I saps que, en l'ordre lògic, els
JOINes resolen dins del pasFROM, abans delWHERE: d'aquí en sortirà la diferència crítica entre posar una condició aONo aWHERE.
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
- Què és SQL?
- Configurar el teu entorn SQL
- Sintaxi bàsica de SQL
- Entendre bases de dades i taules
- El model relacional: claus primàries i foranes
- La base de dades del curs: BotigaVerda
Mòdul 2: Consultes bàsiques de SQL
- Instrucció SELECT
- Àlies, expressions i columnes calculades
- Filtrar dades amb WHERE
- DISTINCT i eliminació de duplicats
- Ordenar dades amb ORDER BY
- Limitar resultats amb LIMIT
Mòdul 3: Treballar amb múltiples taules
- Operacions JOIN
- INNER JOIN
- LEFT JOIN
- RIGHT JOIN
- FULL OUTER JOIN
- SELF JOIN i CROSS JOIN
- Unions de conjunts: UNION, INTERSECT i EXCEPT
Mòdul 4: Filtratge avançat de dades
- Utilitzar LIKE per a coincidència de patrons
- Operadors IN i BETWEEN
- Valors NULL i IS NULL
- Funcions d'agregació: COUNT, SUM, AVG, MIN i MAX
- Agregar dades amb GROUP BY
- Clàusula HAVING
Mòdul 5: Manipulació de dades
- Crear taules i restriccions amb CREATE TABLE
- Instrucció INSERT
- Instrucció UPDATE
- Instrucció DELETE
- Instrucció UPSERT (MERGE)
- Modificar l'esquema: ALTER TABLE i migracions segures
Mòdul 6: Funcions avançades de SQL
- Funcions de cadena
- Funcions numèriques
- Funcions de data i hora
- Conversió de tipus i tractament dels NULL: CAST i COALESCE
- Expressions condicionals
Mòdul 7: Subconsultes i consultes imbricades
- Introducció a les subconsultes
- Subconsultes correlacionades
- EXISTS i NOT EXISTS
- Utilitzar subconsultes en les clàusules SELECT, FROM i WHERE
- Subconsultes o JOIN: quin triar
Mòdul 8: Índexs i optimització del rendiment
- Entendre els índexs
- Creació i gestió d'índexs
- Tipus d'índex i quan no indexar
- Tècniques d'optimització de consultes
- Anàlisi del rendiment de les consultes
Mòdul 9: Transaccions i concurrència
- Introducció a les transaccions
- Propietats ACID
- Instruccions de control de transaccions
- Nivells d'aïllament i anomalies de concurrència
- Gestió de la concurrència: bloquejos i interbloquejos
Mòdul 10: Temes avançats
- Vistes
- Expressions de taula comunes (CTE)
- Funcions de finestra
- Procediments emmagatzemats
- Disparadors (triggers)
- JSON i dades semiestructurades
Mòdul 11: SQL a la pràctica
- Casos d'ús al món real
- Bones pràctiques
- Seguretat: injecció SQL, permisos i rols
- SQL per a l'anàlisi de dades
- SQL en el desenvolupament web
