L'INNER JOIN és el JOIN per defecte, el que ja vas fer servir sense anomenar-lo a la lliçó anterior i el que escriuràs en vuit de cada deu consultes de la teva vida professional. La seva regla és d'una simplicitat absoluta: només sobreviuen les files que troben parella a banda i banda. Tota la resta desapareix, en silenci i sense avís.
I aquí hi ha el parany. Un INNER JOIN mai no dona error per perdre files; simplement et retorna menys de les que esperaves. En aquesta lliçó aprendràs no només a escriure'l, sinó a predir quantes files retorna i per què, que és l'habilitat que distingeix qui entén els JOIN de qui els copia. Veuràs desaparèixer tres clients de BotigaVerda i la meitat de les seves comandes, i entendràs que aquestes desaparicions són la definició mateixa de l'operador.
Contingut
INNER JOINenfront deJOINa seques- El diagrama de conjunts
- Exemples progressius sobre BotigaVerda
- La consulta canònica de quatre taules
- Quines files es perden i per què
- Condicions addicionals:
ONenfront deWHERE - Claus no úniques i la multiplicació de files
- L'ordre de les taules no canvia el resultat
- Errors habituals i consells
- Exercicis
- Conclusió
INNER JOIN enfront de JOIN a seques
INNER JOIN enfront de JOIN a sequesLes dues formes següents són exactament la mateixa consulta:
INNER és opcional perquè l'estàndard SQL defineix el JOIN sense qualificar com a intern. És el contrari del que passa amb LEFT OUTER JOIN, on l'opcional és OUTER.
| Escriptura | Significat | La paraula sobra? |
|---|---|---|
JOIN |
INNER JOIN |
— |
INNER JOIN |
Intern | Sí, INNER és opcional |
LEFT JOIN |
LEFT OUTER JOIN |
— |
LEFT OUTER JOIN |
Extern per l'esquerra | Sí, OUTER és opcional |
Quina escriure? Hi ha dues escoles. Una prefereix JOIN per brevetat; l'altra prefereix INNER JOIN perquè, en una consulta que barreja diversos tipus, veure la paraula INNER al costat d'un LEFT deixa explícita la intenció i evita que algú pensi que s'ha oblidat un modificador.
Convenció del curs: escriurem
JOINquan tota la consulta sigui interna, iINNER JOINexplícit quan convisqui amb unLEFT, unRIGHTo unFULLa la mateixa consulta. És la pràctica més estesa en equips professionals.
- El diagrama de conjunts
La forma clàssica de visualitzar els JOIN és amb dos conjunts que se solapen. L'INNER JOIN retorna només la intersecció:
flowchart LR
subgraph R[" "]
direction LR
A(("clients<br/>sense comandes<br/>❌ fora"))
I(("casen<br/>✅ resultat"))
B(("comandes<br/>sense client<br/>❌ fora"))
end
style A fill:#f8f8f8,stroke:#bbb,stroke-dasharray: 4 4
style I fill:#d9f0d9,stroke:#2b7a2b,stroke-width:3px
style B fill:#f8f8f8,stroke:#bbb,stroke-dasharray: 4 4
I com a flux de files, reprenent el model mental de 03-01:
flowchart LR
A["taula esquerra"] --> C["cartesià"]
B["taula dreta"] --> C
C --> D["filtre ON"]
D --> E["✅ files que casen"]
D --> F["❌ files sense parella<br/>es descarten a banda i banda"]
Aquest quadre vermell —"es descarten a banda i banda"— és tot el que cal recordar de l'INNER JOIN. Les lliçons 03-03, 03-04 i 03-05 consisteixen precisament a recuperar el que aquí es llença.
- Exemples progressius sobre BotigaVerda
3.1. Un producte i el nom de la seva categoria
Ja ho vas veure a 03-01; ho recuperem com a punt de partida, ara ampliat amb el proveïdor. Tres taules, dos ON:
SELECT p.id,
p.nom AS producte,
cat.nom AS categoria,
pr.nom AS proveidor,
p.preu
FROM productes AS p
JOIN categories AS cat ON p.categoria_id = cat.id
JOIN proveidors AS pr ON p.proveidor_id = pr.id
WHERE cat.nom = 'Cosmètica natural'
ORDER BY p.id;| id | producte | categoria | proveidor | preu |
|---|---|---|---|---|
| 6 | Crema facial d'àloe vera 50 ml | Cosmètica natural | Maison Nature | 18.90 |
| 7 | Xampú sòlid de romaní 80 g | Cosmètica natural | Maison Nature | 8.40 |
| 8 | Oli corporal d'ametlles 200 ml | Cosmètica natural | Verde Atlántico | 14.25 |
| 9 | Bàlsam labial de calèndula 15 ml | Cosmètica natural | Maison Nature | 4.60 |
Fixa't en un detall que només és possible amb JOIN: hem filtrat pel nom de la categoria, no pel seu id. Abans d'aquest mòdul hauries hagut de buscar a mà que "Cosmètica natural" és la categoria 2 i escriure WHERE categoria_id = 2. Ara la consulta es llegeix com es pensa.
3.2. Comandes amb el nom complet del client
La pregunta amb què vam tancar el mòdul 2: qui va fer cada comanda.
SELECT co.id AS comanda_id,
co.data_comanda,
c.nom || ' ' || c.cognoms AS client,
c.ciutat,
co.estat,
co.despeses_enviament
FROM comandes AS co
JOIN clients AS c ON co.client_id = c.id
ORDER BY co.id;| comanda_id | data_comanda | client | ciutat | estat | despeses_enviament |
|---|---|---|---|---|---|
| 1 | 2025-03-04 | Lucía Martínez Soler | València | lliurat | 4.95 |
| 2 | 2025-03-12 | Carlos Ferrer Ibáñez | València | lliurat | 0.00 |
| 3 | 2025-04-02 | Marta Sanchis Gil | Castelló | lliurat | 4.95 |
| 4 | 2025-04-19 | Javier Ortega Ruiz | Madrid | lliurat | 4.95 |
| 5 | 2025-05-07 | Lucía Martínez Soler | València | lliurat | 0.00 |
| 6 | 2025-05-23 | Ana Belmonte Roca | Barcelona | cancellat | 4.95 |
| 7 | 2025-06-11 | Pau Llorens Vidal | València | lliurat | 6.50 |
| 8 | 2025-06-28 | Sofia Moreira Costa | Lisboa | lliurat | 9.90 |
| 9 | 2025-07-15 | Tiago Almeida Nunes | Porto | lliurat | 9.90 |
| 10 | 2025-08-03 | Camille Dubois | Lió | lliurat | 12.50 |
| 11 | 2025-09-09 | Carlos Ferrer Ibáñez | València | lliurat | 0.00 |
| 12 | 2025-10-01 | Julien Moreau | París | lliurat | 12.50 |
| 13 | 2025-10-22 | Elena Navarro Puig | Alacant | lliurat | 4.95 |
| 14 | 2025-11-14 | Diego Ramos Herrera | Sevilla | lliurat | 4.95 |
| 15 | 2025-12-02 | Lucía Martínez Soler | València | lliurat | 0.00 |
| 16 | 2025-12-19 | Javier Ortega Ruiz | Madrid | enviat | 4.95 |
| 17 | 2026-01-13 | Sofia Moreira Costa | Lisboa | enviat | 9.90 |
| 18 | 2026-01-27 | Ana Belmonte Roca | Barcelona | pagat | 4.95 |
| 19 | 2026-02-09 | Pau Llorens Vidal | València | pagat | 4.95 |
| 20 | 2026-02-21 | Camille Dubois | Lió | pendent | 12.50 |
20 files, les mateixes que té comandes. Ja hi és per fi "Camille Dubois" on abans hi havia un 9.
Dues observacions:
- Els noms es repeteixen: Lucía Martínez Soler apareix tres vegades perquè va fer tres comandes (1, 5 i 15). És normal: unim pel costat "molts" de la relació 1:N.
- La concatenació
c.nom || ' ' || c.cognomsés la de 02-02, amb el seu advertiment sobreNULLinclòs. Aquí és segura perquè totes dues columnes sónNOT NULL.
3.3. Línies de comanda amb nom de producte i import
SELECT lc.id AS linia_id,
lc.comanda_id,
p.nom AS producte,
lc.quantitat,
lc.preu_unitari,
lc.descompte,
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
ORDER BY lc.id
LIMIT 10;| linia_id | comanda_id | producte | quantitat | preu_unitari | descompte | import |
|---|---|---|---|---|---|---|
| 1 | 1 | Oli d'oliva verge extra 500 ml | 2 | 11.95 | 0.00 | 23.90 |
| 2 | 1 | Arròs integral ecològic 1 kg | 3 | 3.90 | 0.00 | 11.70 |
| 3 | 1 | Infusió de camamilla ecològica 20 u | 2 | 3.25 | 0.00 | 6.50 |
| 4 | 2 | Crema facial d'àloe vera 50 ml | 1 | 17.50 | 0.00 | 17.50 |
| 5 | 2 | Bàlsam labial de calèndula 15 ml | 2 | 4.60 | 0.00 | 9.20 |
| 6 | 3 | Tomàquet triturat ecològic 400 g | 6 | 1.95 | 0.10 | 10.53 |
| 7 | 3 | Pasta d'espelta 500 g | 4 | 2.80 | 0.00 | 11.20 |
| 8 | 3 | Arròs integral ecològic 1 kg | 2 | 3.90 | 0.00 | 7.80 |
| 9 | 4 | Te verd matcha cerimonial 30 g | 1 | 22.00 | 0.00 | 22.00 |
| 10 | 4 | Mel de tarongina crua 500 g | 1 | 9.75 | 0.00 | 9.75 |
(10 primeres de 47 files.)
Aquí es veu una cosa que al mòdul 2 només podíem intuir: la línia 1 té preu_unitari 11.95, mentre que l'oli costa avui 12,50 €. És el preu històric del qual parlava 01-06. Un error clàssic seria calcular l'import amb p.preu en comptes de lc.preu_unitari: obtindries 25,00 € en lloc de 23,90 € i estaries reescrivint la història comercial de l'empresa.
Regla del curs: l'import d'una línia es calcula sempre amb
lc.preu_unitari, mai ambp.preu. La taulaproductesdiu quant costa avui;linies_comandadiu quant es va cobrar aleshores.
- La consulta canònica de quatre taules
Aquesta és la consulta més important del curs. Respon a "què va comprar cada client i per quant?" i necessita quatre taules: el detall és a linies_comanda, el client penja de comandes i el nom del producte de productes.
flowchart LR
LC["linies_comanda<br/>(detall: 47 files)"] -->|"lc.comanda_id = co.id"| CO["comandes"]
CO -->|"co.client_id = c.id"| C["clients"]
LC -->|"lc.producte_id = p.id"| P["productes"]
Observa la forma del camí: no és una cadena lineal, és una estrella amb linies_comanda al centre. D'ella en surten dues branques: una cap a la comanda i el seu client, una altra cap al producte. Això és normal quan la taula de partida té diverses claus foranes.
SELECT co.id AS comanda_id,
co.data_comanda,
c.nom || ' ' || c.cognoms AS client,
p.nom AS producte,
lc.quantitat,
ROUND(lc.quantitat * lc.preu_unitari * (1 - lc.descompte), 2) AS import
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
JOIN productes AS p ON lc.producte_id = p.id
ORDER BY co.id, lc.id
LIMIT 12;| comanda_id | data_comanda | client | producte | quantitat | import |
|---|---|---|---|---|---|
| 1 | 2025-03-04 | Lucía Martínez Soler | Oli d'oliva verge extra 500 ml | 2 | 23.90 |
| 1 | 2025-03-04 | Lucía Martínez Soler | Arròs integral ecològic 1 kg | 3 | 11.70 |
| 1 | 2025-03-04 | Lucía Martínez Soler | Infusió de camamilla ecològica 20 u | 2 | 6.50 |
| 2 | 2025-03-12 | Carlos Ferrer Ibáñez | Crema facial d'àloe vera 50 ml | 1 | 17.50 |
| 2 | 2025-03-12 | Carlos Ferrer Ibáñez | Bàlsam labial de calèndula 15 ml | 2 | 9.20 |
| 3 | 2025-04-02 | Marta Sanchis Gil | Tomàquet triturat ecològic 400 g | 6 | 10.53 |
| 3 | 2025-04-02 | Marta Sanchis Gil | Pasta d'espelta 500 g | 4 | 11.20 |
| 3 | 2025-04-02 | Marta Sanchis Gil | Arròs integral ecològic 1 kg | 2 | 7.80 |
| 4 | 2025-04-19 | Javier Ortega Ruiz | Te verd matcha cerimonial 30 g | 1 | 22.00 |
| 4 | 2025-04-19 | Javier Ortega Ruiz | Mel de tarongina crua 500 g | 1 | 9.75 |
| 5 | 2025-05-07 | Lucía Martínez Soler | Detergent ecològic concentrat 1 L | 1 | 11.20 |
| 5 | 2025-05-07 | Lucía Martínez Soler | Fregall vegetal de lufa (pack 3) | 2 | 11.00 |
(12 primeres de 47 files.)
47 files: una per línia de comanda. Desa aquesta consulta, perquè és la base de gairebé tot el que ve després. Al mòdul 4 hi afegiràs GROUP BY c.id i SUM(import) per respondre "quant ha gastat cada client?"; al mòdul 7 la faràs servir com a subconsulta; al mòdul 10 la convertiràs en una vista. L'esquelet no canvia.
Quatre taules, tres condicions ON. Torna a comprovar-se la regla de 03-01: N taules, N-1 emparellaments.
- Quines files es perden i per què
Arribem al cor de la lliçó. Un INNER JOIN descarta silenciosament les files sense parella, i a BotigaVerda això té dues conseqüències molt visibles.
5.1. Els tres clients que desapareixen
clients té 15 files. Vegem qui sobreviu a un INNER JOIN amb comandes:
SELECT DISTINCT c.id,
c.nom,
c.cognoms
FROM clients AS c
JOIN comandes AS co ON co.client_id = c.id
ORDER BY c.id;| id | nom | cognoms |
|---|---|---|
| 1 | Lucía | Martínez Soler |
| 2 | Carlos | Ferrer Ibáñez |
| 3 | Marta | Sanchis Gil |
| 4 | Javier | Ortega Ruiz |
| 5 | Ana | Belmonte Roca |
| 6 | Pau | Llorens Vidal |
| 7 | Sofia | Moreira Costa |
| 8 | Tiago | Almeida Nunes |
| 9 | Camille | Dubois |
| 10 | Julien | Moreau |
| 11 | Elena | Navarro Puig |
| 12 | Diego | Ramos Herrera |
12 files, no 15. Falten els clients 13 (Núria Bosch Ferrer), 14 (Hugo Iglesias Pardo) i 15 (Inés Carrasco Vega). Per què? Perquè cap d'ells no ha fet cap comanda, així que al producte cartesià no existeix cap combinació en què co.client_id valgui 13, 14 o 15. La condició ON no les troba i desapareixen.
El balanç de recomptes:
| Consulta | Files | Què significa |
|---|---|---|
SELECT ... FROM clients |
15 | Tots els clients |
SELECT ... FROM clients JOIN comandes ON ... |
20 | Una fila per comanda, no per client |
SELECT DISTINCT c.id ... FROM clients JOIN comandes ON ... |
12 | Clients amb almenys una comanda |
Les tres xifres són diferents i totes tres són correctes: responen a preguntes diferents. Si et demanen "el llistat de clients amb la seva activitat" i lliures 12 de 15, has esborrat de l'informe tres persones.
Avís: aquest
DISTINCTés exactament el símptoma del qual parlava 02-04. Apareix aquí perquè estem fent servir una eina —l'INNER JOIN— que no és l'adequada per a la pregunta "quins clients han comprat". L'eina correcta arriba a 03-03.
5.2. Les deu comandes sense comercial
El mateix fenomen, des de l'altre costat. comandes.empleat_id admet NULL perquè les comandes web no porten comercial assignat:
SELECT co.id AS comanda_id,
co.data_comanda,
e.nom || ' ' || e.cognoms AS comercial,
e.carrec
FROM comandes AS co
JOIN empleats AS e ON co.empleat_id = e.id
ORDER BY co.id;| comanda_id | data_comanda | comercial | carrec |
|---|---|---|---|
| 2 | 2025-03-12 | Óscar Peris Blasco | Comercial |
| 4 | 2025-04-19 | Laia Puig Sanchis | Comercial |
| 6 | 2025-05-23 | Óscar Peris Blasco | Comercial |
| 8 | 2025-06-28 | Laia Puig Sanchis | Comercial |
| 10 | 2025-08-03 | Óscar Peris Blasco | Comercial |
| 12 | 2025-10-01 | Laia Puig Sanchis | Comercial |
| 14 | 2025-11-14 | Marc Estévez Roig | Atenció al client |
| 16 | 2025-12-19 | Óscar Peris Blasco | Comercial |
| 18 | 2026-01-27 | Laia Puig Sanchis | Comercial |
| 20 | 2026-02-21 | Marc Estévez Roig | Atenció al client |
10 files de 20. Han desaparegut les comandes 1, 3, 5, 7, 9, 11, 13, 15, 17 i 19: totes les que tenen empleat_id IS NULL.
El mecanisme és el que vas veure a 02-03 amb empleat_id = NULL: NULL no és igual a res, ni tan sols a un altre NULL. La condició ON co.empleat_id = e.id s'avalua a NULL (que no és TRUE) per a aquelles deu files, així que cap parella no les salva.
I aquí hi ha el dany real: la meitat de la facturació de BotigaVerda ha desaparegut de l'informe. Si la direcció pregunta "quantes comandes hem tingut aquest any?" i respons amb aquesta consulta, la teva resposta serà la meitat de la veritat.
flowchart TD
A["20 comandes"] --> B{"empleat_id<br/>té valor?"}
B -->|"sí (10)"| C["troben parella<br/>✅ surten al resultat"]
B -->|"NULL (10)"| D["cap comparació no és TRUE<br/>❌ es perden"]
5.3. Les dues causes de pèrdua
Resumint, un INNER JOIN perd files per dos motius:
| Causa | Exemple a BotigaVerda | Solució |
|---|---|---|
La FK és NULL |
Les 10 comandes sense empleat_id |
LEFT JOIN des de comandes (03-03) |
| No existeix cap fila filla que apunti a aquesta | Els clients 13, 14 i 15; els productes 13, 19 i 20 | LEFT JOIN des de la taula pare (03-03) |
En tots dos casos la resposta és la mateixa família d'operadors, i per això existeixen les lliçons 03-03, 03-04 i 03-05. L'INNER JOIN no està malament: simplement respon a la pregunta "dona'm el que casa", i de vegades la pregunta del negoci és una altra.
- Condicions addicionals:
ON enfront de WHERE
ON enfront de WHERERes no impedeix posar condicions extra dins de l'ON, més enllà de l'emparellament. Aquestes dues consultes demanen el mateix: les comandes de clients portuguesos.
-- Condició a l'ON
SELECT co.id, co.data_comanda, c.nom, c.pais
FROM comandes AS co
JOIN clients AS c
ON co.client_id = c.id
AND c.pais = 'Portugal'
ORDER BY co.id;| id | data_comanda | nom | pais |
|---|---|---|---|
| 8 | 2025-06-28 | Sofia | Portugal |
| 9 | 2025-07-15 | Tiago | Portugal |
| 17 | 2026-01-13 | Sofia | Portugal |
-- Condició al WHERE
SELECT co.id, co.data_comanda, c.nom, c.pais
FROM comandes AS co
JOIN clients AS c ON co.client_id = c.id
WHERE c.pais = 'Portugal'
ORDER BY co.id;| id | data_comanda | nom | pais |
|---|---|---|---|
| 8 | 2025-06-28 | Sofia | Portugal |
| 9 | 2025-07-15 | Tiago | Portugal |
| 17 | 2026-01-13 | Sofia | Portugal |
Resultats idèntics. I no és casualitat: en un INNER JOIN són sempre equivalents. Torna al diagrama de l'ordre lògic de 03-01 i entendràs per què. En un JOIN intern no existeix el pas 1d —no es reintrodueix cap fila sense parella—, així que filtrar durant l'emparellament o just després produeix el mateix conjunt.
Si són equivalents, on posar-les? Per llegibilitat:
| Tipus de condició | On posar-la | Exemple |
|---|---|---|
| Relaciona dues taules (emparellament) | ON |
co.client_id = c.id |
| Restringeix quines files ens interessen | WHERE |
c.pais = 'Portugal' |
I ara l'avís més important d'aquesta lliçó: aquesta equivalència és EXCLUSIVA de l'
INNER JOIN. En unLEFT JOIN, moure una condició de l'ONalWHEREcanvia el resultat, i el canvia d'una manera que sembla una fallada de dades i no una fallada de consulta. La lliçó 03-03 ho demostra amb la mateixa pregunta escrita de les dues maneres i els seus dos resultats diferents. Agafa des d'ara el costum de separar emparellament i filtratge: quan arribis alLEFT JOIN, aquest costum et salvarà.
- Claus no úniques i la multiplicació de files
Fins ara tots els JOIN de la lliçó han conservat el nombre de files de la taula de partida. Això passa quan uneixes des del costat "molts" cap al costat "un" (de productes a categories, de comandes a clients): cada fila troba exactament una parella.
A l'inrevés, la cosa canvia.
SELECT co.id AS comanda_id,
co.data_comanda,
co.estat,
lc.id AS linia_id,
lc.producte_id,
lc.quantitat
FROM comandes AS co
JOIN linies_comanda AS lc ON lc.comanda_id = co.id
WHERE co.id <= 3
ORDER BY co.id, lc.id;| comanda_id | data_comanda | estat | linia_id | producte_id | quantitat |
|---|---|---|---|---|---|
| 1 | 2025-03-04 | lliurat | 1 | 1 | 2 |
| 1 | 2025-03-04 | lliurat | 2 | 2 | 3 |
| 1 | 2025-03-04 | lliurat | 3 | 14 | 2 |
| 2 | 2025-03-12 | lliurat | 4 | 6 | 1 |
| 2 | 2025-03-12 | lliurat | 5 | 9 | 2 |
| 3 | 2025-04-02 | lliurat | 6 | 5 | 6 |
| 3 | 2025-04-02 | lliurat | 7 | 4 | 4 |
| 3 | 2025-04-02 | lliurat | 8 | 2 | 2 |
Tres comandes han produït vuit files. La comanda 1 apareix tres vegades (té tres línies), la 2 dues vegades i la 3 tres vegades. Les dades de capçalera —data, estat, i també despeses_enviament si les haguéssim demanat— es repeteixen a cada línia.
La regla general:
En unir per una columna no única al costat dret, cada fila de l'esquerra es duplica tantes vegades com parelles trobi.
Direcció del JOIN |
Efecte sobre el recompte | Exemple |
|---|---|---|
| Molts → un (FK → PK) | Es conserva | linies_comanda JOIN productes: 47 → 47 |
| Un → molts (PK → FK) | Es multiplica | comandes JOIN linies_comanda: 20 → 47 |
| Molts → molts (cap columna única) | Es dispara | Compte |
Per què això és la causa núm. 1 de sumes inflades
Això sembla inofensiu mentre només mires files de detall. Es torna perillós al mòdul 4, quan comencis a sumar. Imagina aquesta consulta:
-- ⚠️ INCORRECTA (avançament del mòdul 4): les despeses d'enviament queden inflades
SELECT SUM(co.despeses_enviament)
FROM comandes AS co
JOIN linies_comanda AS lc ON lc.comanda_id = co.id;Les despeses d'enviament de la comanda 1 són 4,95 € una vegada, però després del JOIN aquesta xifra apareix en tres files. La suma donaria 14,85 € només per a aquella comanda: el triple del real. I no hi haurà cap error, cap avís, només un número equivocat en un informe de direcció.
El mateix passaria en sumar salari després d'unir empleats amb comandes, o en comptar clients després d'unir clients amb linies_comanda.
Aprèn a detectar-ho ara: després de cada
JOINque afegeixis, pregunta't "quantes files de la dreta hi pot haver per cada fila de l'esquerra?". Si la resposta és "més d'una", qualsevol valor de l'esquerra que sumis després estarà multiplicat. Al mòdul 4 veuràs les tècniques per evitar-ho; de moment n'hi ha prou de reconèixer el símptoma.
Un truc de verificació que pots aplicar avui mateix: si el recompte de files de la teva consulta coincideix amb el de la taula de detall (47 per a linies_comanda), estàs al nivell de granularitat correcte per treballar amb imports de línia.
- L'ordre de les taules no canvia el resultat
En un INNER JOIN, A JOIN B i B JOIN A retornen el mateix conjunt de files. L'operació és commutativa.
-- Aquestes dues són equivalents
FROM comandes AS co JOIN clients AS c ON co.client_id = c.id
FROM clients AS c JOIN comandes AS co ON co.client_id = c.idTotes dues retornen les mateixes 20 files. L'única cosa que pot canviar és l'ordre de les columnes si fas servir SELECT *, i l'ordre de les files si no hi poses ORDER BY (el de sempre des de 02-01).
Amb tres o més taules també és associativa: (A JOIN B) JOIN C equival a A JOIN (B JOIN C), sempre que les condicions ON estiguin ben posades.
| Propietat | INNER JOIN |
LEFT JOIN |
|---|---|---|
Commutativa (A ⋈ B = B ⋈ A) |
Sí | No |
| Associativa | Sí | Només amb compte |
Que sigui commutatiu té dues conseqüències pràctiques:
- Escriu les taules en l'ordre en què s'entén la pregunta. Si l'informe és "comandes amb el seu client", comença per
comandes. Si és "clients i les seves comandes", comença perclients. El resultat és el mateix i la consulta es llegeix millor. - L'optimitzador no et fa cas. PostgreSQL reordena lliurement els
INNER JOINper triar el pla més barat: pot començar per la taula més petita, o per la que tingui el filtre més selectiu, amb independència de com ho escriguis. El teu ordre és documentació per a humans, no una instrucció per al motor. (Ho veuràs al mòdul 8.)
I un advertiment per al que ve. El LEFT JOIN no és commutatiu: A LEFT JOIN B i B LEFT JOIN A retornen coses diferents. Allà l'ordre en què escrius les taules és part del significat, no de l'estil.
Errors habituals i consells
- Donar per fet que l'
INNER JOINconserva totes les files. Perd les que no casen a cap dels dos costats, sense dir res. Comprova el recompte contra la taula de partida. - Oblidar que una FK amb
NULLmai no casa. Les 10 comandes web desapareixen en unir ambempleats.NULL = 4no és fals: ésNULL, iNULLno ésTRUE. - Calcular l'import amb
p.preuen comptes delc.preu_unitari. Retorna el preu d'avui aplicat a una venda de fa un any. Amb la línia 1 la diferència és d'1,10 €; amb un catàleg real, de milers d'euros. - Sumar valors de capçalera després d'unir amb el detall.
SUM(co.despeses_enviament)després deJOIN linies_comandacompta cada comanda tantes vegades com línies tingui. És la causa núm. 1 d'informes inflats. - Posar el
DISTINCTcom a pedaç. Si necessitesDISTINCTper arreglar unJOIN, gairebé sempre elJOINés el que està mal plantejat (02-04). Pensa quina granularitat vol de debò la pregunta. - Creure que l'equivalència
ON/WHEREés general. Només val per aINNER JOIN. EnLEFT JOINcanvia el resultat (03-03). - Emparellar per columnes del tipus correcte però del concepte equivocat.
ON lc.producte_id = co.idno dona error, compara enters amb enters, i retorna dades sense sentit. - Consell: valida cada
JOINper separat. Escriu primerFROM linies_comanda lc JOIN comandes co ON ...ambLIMIT 5, comprova-ho, i només llavors afegeix la tercera taula. Depurar una consulta de cinc taules escrita d'una tirada és un suplici. - Consell: memoritza els tres recomptes de BotigaVerda. 20 productes, 20 comandes, 47 línies. Si una consulta de detall no retorna 47 files, saps immediatament que passa alguna cosa.
- Consell: posa nom als àlies de columna quan uneixis taules amb columnes homònimes.
c.nom AS clientip.nom AS producteeviten que el resultat tingui dues columnes anomenadesnom.
Exercicis
Exercici 1
L'equip de producte vol revisar les opinions rebudes. Escriu una consulta que retorni, per a cada ressenya: el seu id, el nom del producte ressenyat, el nom complet de qui la va escriure, la puntuació i la data. Ordena per id de ressenya.
Després respon: quantes files retorna, i per què aquest número no coincideix amb el nombre de productes del catàleg?
Exercici 2
Administració necessita el detall de les devolucions. Escriu una consulta que mostri, per a cada devolució: el seu id, la data, l'import, el motiu, l'id i l'estat de la comanda retornada, i el nom complet del client afectat.
Indica quantes taules has necessitat, quantes condicions ON i per què.
Exercici 3
Camille Dubois (client 9) ha trucat reclamant el detall de tot el que ha comprat. Fent servir la consulta canònica de quatre taules de la secció 4, obtén el seu historial complet: comanda, data, estat de la comanda, producte, quantitat i import de la línia.
Després respon a aquestes dues preguntes:
- Quantes files retorna i quantes comandes representen?
- Si en comptes d'
INNER JOINentrecomandesiclientshaguessis unitcomandesambempleats, apareixerien les mateixes comandes de la Camille? Raona la resposta mirant les dades.
Solucions
Solució 1
SELECT r.id AS ressenya_id,
p.nom AS producte,
c.nom || ' ' || c.cognoms AS client,
r.puntuacio,
r.data
FROM ressenyes AS r
JOIN productes AS p ON r.producte_id = p.id
JOIN clients AS c ON r.client_id = c.id
ORDER BY r.id;| ressenya_id | producte | client | puntuacio | data |
|---|---|---|---|---|
| 1 | Oli d'oliva verge extra 500 ml | Lucía Martínez Soler | 5 | 2025-03-15 |
| 2 | Arròs integral ecològic 1 kg | Lucía Martínez Soler | 4 | 2025-03-16 |
| 3 | Crema facial d'àloe vera 50 ml | Carlos Ferrer Ibáñez | 5 | 2025-03-25 |
| 4 | Tomàquet triturat ecològic 400 g | Marta Sanchis Gil | 3 | 2025-04-12 |
| 5 | Te verd matcha cerimonial 30 g | Javier Ortega Ruiz | 5 | 2025-05-02 |
| 6 | Kombutxa de gingebre 750 ml | Pau Llorens Vidal | 2 | 2025-06-20 |
| 7 | Oli d'oliva verge extra 500 ml | Sofia Moreira Costa | 5 | 2025-07-08 |
| 8 | Raspall de dents de bambú | Tiago Almeida Nunes | 4 | 2025-07-26 |
| 9 | Crema facial d'àloe vera 50 ml | Camille Dubois | 4 | 2025-08-14 |
| 10 | Arròs integral ecològic 1 kg | Carlos Ferrer Ibáñez | 5 | 2025-09-19 |
| 11 | Bosses reutilitzables de cotó (pack 5) | Elena Navarro Puig | 3 | 2025-11-03 |
| 12 | Detergent ecològic concentrat 1 L | Javier Ortega Ruiz | 4 | 2026-01-10 |
12 files, exactament les de ressenyes. Partim d'aquesta taula i les seves dues claus foranes (producte_id, client_id) són obligatòries i vàlides: cada ressenya troba un producte i un client, un i només un.
Per què no coincideix amb els 20 productes: perquè la consulta va de ressenyes a productes, no a l'inrevés. Només apareixen els 9 productes diferents que tenen alguna ressenya (l'oli, l'arròs i la crema hi apareixen dues vegades cadascun). Els 11 productes sense cap ressenya —entre ells el desodorant i les càpsules d'espirulina— no existeixen per a aquesta consulta. Per veure'ls caldria partir de productes i fer servir un LEFT JOIN: és just el que fa la lliçó següent.
Solució 2
SELECT d.id AS devolucio_id,
d.data,
d.import,
d.motiu,
co.id AS comanda_id,
co.estat,
c.nom || ' ' || c.cognoms AS client
FROM devolucions AS d
JOIN comandes AS co ON d.comanda_id = co.id
JOIN clients AS c ON co.client_id = c.id
ORDER BY d.id;| devolucio_id | data | import | motiu | comanda_id | estat | client |
|---|---|---|---|---|---|---|
| 1 | 2025-05-25 | 26.75 | Comanda cancel·lada pel client abans de l'enviament | 6 | cancellat | Ana Belmonte Roca |
| 2 | 2025-08-11 | 34.02 | Producte malmès durant el transport | 10 | lliurat | Camille Dubois |
| 3 | 2025-10-30 | 19.80 | El format no correspon al que s'esperava | 13 | lliurat | Elena Navarro Puig |
Tres taules i dues condicions ON, seguint la regla N-1 de 03-01. En calen tres perquè el client no és assolible des de devolucions en un sol salt: devolucions només coneix comanda_id, i és comandes qui coneix client_id. El camí obligatori és devolucions → comandes → clients.
Un detall que valida les dades: la devolució 2 val 34,02 €, exactament l'import de la línia 24 (2 unitats de crema facial a 18,90 € amb 10 % de descompte). No és casualitat: es va retornar aquell producte concret de la comanda de la Camille.
Solució 3
SELECT co.id AS comanda_id,
co.data_comanda,
co.estat,
p.nom AS producte,
lc.quantitat,
ROUND(lc.quantitat * lc.preu_unitari * (1 - lc.descompte), 2) AS import
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
JOIN productes AS p ON lc.producte_id = p.id
WHERE c.id = 9
ORDER BY co.id, lc.id;| comanda_id | data_comanda | estat | producte | quantitat | import |
|---|---|---|---|---|---|
| 10 | 2025-08-03 | lliurat | Crema facial d'àloe vera 50 ml | 2 | 34.02 |
| 10 | 2025-08-03 | lliurat | Oli corporal d'ametlles 200 ml | 1 | 14.25 |
| 20 | 2026-02-21 | pendent | Arròs integral ecològic 1 kg | 4 | 15.60 |
| 20 | 2026-02-21 | pendent | Raspall de dents de bambú | 2 | 7.00 |
1. 4 files que representen 2 comandes: la 10 (lliurada, dues línies) i la 20 (pendent, dues línies). És la multiplicació de files de la secció 7 en acció: el nombre de files és el de línies, no el de comandes. Si volguessis el nombre de comandes de la Camille, aquesta consulta no és l'eina: et caldria COUNT(DISTINCT co.id) del mòdul 4.
2. No. La comanda 10 té empleat_id = 4 (Óscar Peris Blasco) i apareixeria; la comanda 20 té empleat_id = 6 (Marc Estévez Roig) i també. En aquest cas concret sí que sortirien totes dues... però és pura sort: totes dues comandes de la Camille tenen comercial assignat. Prova amb Lucía Martínez Soler (client 1), les tres comandes de la qual —1, 5 i 15— són totes web amb empleat_id IS NULL: un INNER JOIN amb empleats faria desaparèixer el 100 % del seu historial. Aquest és el perill que motiva la lliçó següent.
Conclusió
L'INNER JOIN ja no té secrets:
JOINiINNER JOINsón el mateix. El curs fa servirJOINen consultes purament internes iINNER JOINexplícit quan hi conviuen diversos tipus.- Retorna només la intersecció: les files que troben parella a banda i banda. Tot el que no casa es descarta en silenci.
- Saps escriure cadenes de tres i quatre taules i tens la consulta canònica de detall de vendes (
linies_comanda+comandes+clients+productes, 47 files), que reutilitzaràs als mòduls 4, 7 i 10. - Saps què es perd: els clients 13, 14 i 15 en unir
clientsambcomandes(12 clients de 15), i les 10 comandes web en unircomandesambempleats(10 files de 20). Les dues causes són la FK ambNULLi l'absència de fila filla. - En un
INNER JOIN, posar una condició extra aONo aWHEREés equivalent; en unLEFT JOINno ho serà, i aquest és el parany de la lliçó vinent. - Unir cap al costat "molts" multiplica files: 20 comandes es converteixen en 47 en afegir
linies_comanda. És la causa número u de les sumes inflades que veuràs al mòdul 4. - L'
INNER JOINés commutatiu i associatiu: l'ordre de les taules és qüestió de llegibilitat, no de significat. ElLEFT JOINno ho serà.
A la lliçó següent, LEFT JOIN, recuperarem tot el que aquí hem llençat. Tornaran la Núria, l'Hugo i la Inés amb les seves comandes buides; tornaran les espelmes de soja, el desodorant i l'espirulina que ningú no ha comprat mai; tornaran les deu comandes web amb el seu comercial a NULL. Aprendràs el patró anti-join per respondre directament a "quins clients no han comprat mai?" i veuràs, amb la mateixa consulta escrita de dues formes, per què col·locar una condició a ON o a WHERE deixa de ser indiferent.
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
