Aquests són els dos JOIN que més desconcerten qui comença, i per motius oposats. El SELF JOIN sembla impossible —com s'uneix una taula amb si mateixa sense entrar en un bucle?— i resulta ser l'única forma de recórrer les relacions reflexives que fa temps que veus des de 01-05: empleats.cap_id i clients.referit_per_id. El CROSS JOIN sembla un error —és el producte cartesià que a 03-01 presentàvem com a accident— i resulta ser una eina deliberada per generar combinacions completes.
Cap dels dos no és un "tipus" nou d'emparellament. El SELF JOIN és una tècnica que pot fer servir qualsevol tipus (INNER, LEFT...); el CROSS JOIN és el cas degenerat d'un JOIN sense condició.
Contingut
SELF JOIN: unir una taula amb si mateixa- La jerarquia d'
empleats - La xarxa de referits de
clients SELF JOINno jeràrquic: parelles dins del mateix grup- Empleats de la mateixa ciutat
- Jerarquies de profunditat arbitrària
CROSS JOIN: el producte cartesià deliberat- Quan és útil i quan és un accident
CROSS JOINambgenerate_seriesper a calendaris- Errors habituals i consells
- Exercicis
- Conclusió
SELF JOIN: unir una taula amb si mateixa
SELF JOIN: unir una taula amb si mateixaUn SELF JOIN no té sintaxi pròpia. És un JOIN normal en què les dues taules són la mateixa:
Res de bucles infinits: el motor tracta cada aparició de la taula com una relació independent. És exactament el producte cartesià de 03-01, amb empleats als dos costats, filtrat per la condició de l'ON.
flowchart LR
A["empleats<br/>com a 'e'<br/>8 files"] --> C["cartesià<br/>8 × 8 = 64 files"]
B["empleats<br/>com a 'cap'<br/>8 files"] --> C
C --> D["filtre ON<br/>e.cap_id = cap.id"]
D --> E["7 files<br/>(la Rosa no té cap)"]
Per què els àlies deixen de ser opcionals
A 03-01 vam dir que els àlies de taula eren una comoditat. En un SELF JOIN són imprescindibles, i per una raó física: sense ells, el nom empleats designaria dues coses diferents alhora.
PostgreSQL ni tan sols intenta endevinar-ho: s'hi nega. Amb àlies diferents, cada aparició té identitat pròpia i tot funciona.
Convenció del curs: en un
SELF JOIN, els àlies no s'abreugen per inicial sinó que es nomenen pel paper que fa cada còpia.empleats AS eiempleats AS cap;clients AS ciclients AS ref;productes AS p1iproductes AS p2quan els dos papers són simètrics. Un àlies come1/e2és acceptable en parelles simètriques, peròe/capés sempre més llegible quee1/e2quan els papers són diferents.
- La jerarquia d'
empleats
empleatsempleats.cap_id és una clau forana que apunta a empleats.id: la relació reflexiva 1:N que vas dibuixar al diagrama ER de 01-06. Cada empleat té com a molt un cap, i un cap pot tenir diversos subordinats.
2.1. Amb INNER JOIN: es perd la directora
SELECT e.id,
e.nom || ' ' || e.cognoms AS empleat,
e.carrec,
cap.nom || ' ' || cap.cognoms AS cap
FROM empleats AS e
JOIN empleats AS cap ON e.cap_id = cap.id
ORDER BY e.id;| id | empleat | carrec | cap |
|---|---|---|---|
| 2 | Andrés Company Talens | Responsable de vendes | Rosa Alcázar Vives |
| 3 | Beatriz Nadal Ripoll | Responsable de logística | Rosa Alcázar Vives |
| 4 | Óscar Peris Blasco | Comercial | Andrés Company Talens |
| 5 | Laia Puig Sanchis | Comercial | Andrés Company Talens |
| 6 | Marc Estévez Roig | Atenció al client | Andrés Company Talens |
| 7 | Irene Salvador Mira | Operària de magatzem | Beatriz Nadal Ripoll |
| 8 | Daniel Vercher Lluch | Analista de dades | Rosa Alcázar Vives |
7 files de 8. Falta Rosa Alcázar Vives, la directora general, perquè el seu cap_id és NULL i no casa amb ningú. És exactament el mecanisme de 03-02: una FK nul·la mai no troba parella.
I és un resultat perillós, perquè sembla complet. Un organigrama que no inclou la directora general és un organigrama equivocat.
2.2. Amb LEFT JOIN: l'organigrama complet
-- ✅ CORRECTA
SELECT e.id,
e.nom || ' ' || e.cognoms AS empleat,
e.carrec,
cap.nom || ' ' || cap.cognoms AS cap,
cap.carrec AS carrec_cap
FROM empleats AS e
LEFT JOIN empleats AS cap ON e.cap_id = cap.id
ORDER BY e.id;| id | empleat | carrec | cap | carrec_cap |
|---|---|---|---|---|
| 1 | Rosa Alcázar Vives | Directora general | (null) | (null) |
| 2 | Andrés Company Talens | Responsable de vendes | Rosa Alcázar Vives | Directora general |
| 3 | Beatriz Nadal Ripoll | Responsable de logística | Rosa Alcázar Vives | Directora general |
| 4 | Óscar Peris Blasco | Comercial | Andrés Company Talens | Responsable de vendes |
| 5 | Laia Puig Sanchis | Comercial | Andrés Company Talens | Responsable de vendes |
| 6 | Marc Estévez Roig | Atenció al client | Andrés Company Talens | Responsable de vendes |
| 7 | Irene Salvador Mira | Operària de magatzem | Beatriz Nadal Ripoll | Responsable de logística |
| 8 | Daniel Vercher Lluch | Analista de dades | Rosa Alcázar Vives | Directora general |
8 files: l'equip complet. El NULL de la Rosa significa "arrel de la jerarquia", i en un informe es presentaria com a "—" o "Direcció" amb COALESCE (06-04).
Regla: en un
SELF JOINjeràrquic, l'arrel de l'arbre sempre té la FK aNULL. Si vols que aparegui, elJOINha de serLEFT. És l'error més freqüent en construir organigrames, arbres de categories o estructures de carpetes.
2.3. Donar-hi la volta: cada cap amb els seus subordinats
Canviant el sentit de la condició s'obté la relació inversa. N'hi ha prou de llegir l'ON a l'inrevés:
SELECT cap.nom || ' ' || cap.cognoms AS cap,
sub.nom || ' ' || sub.cognoms AS subordinat,
sub.carrec
FROM empleats AS cap
JOIN empleats AS sub ON sub.cap_id = cap.id
ORDER BY cap.id, sub.id;Retorna les mateixes 7 parelles, presentades des del punt de vista del cap. Fixa't que la condició ON és idèntica; l'única cosa que canvia és quin àlies es diu com i quines columnes es projecten. És la mateixa lliçó de 03-04 sobre LEFT i RIGHT: l'emparellament no canvia, canvia la lectura.
- La xarxa de referits de
clients
clientsclients.referit_per_id funciona igual, però amb un matís de negoci diferent: aquí el NULL no significa "arrel d'una jerarquia" sinó "va arribar pel seu compte".
SELECT c.id,
c.nom || ' ' || c.cognoms AS client,
c.ciutat,
ref.nom || ' ' || ref.cognoms AS referit_per
FROM clients AS c
LEFT JOIN clients AS ref ON c.referit_per_id = ref.id
ORDER BY c.id;| id | client | ciutat | referit_per |
|---|---|---|---|
| 1 | Lucía Martínez Soler | València | (null) |
| 2 | Carlos Ferrer Ibáñez | València | Lucía Martínez Soler |
| 3 | Marta Sanchis Gil | Castelló | Lucía Martínez Soler |
| 4 | Javier Ortega Ruiz | Madrid | (null) |
| 5 | Ana Belmonte Roca | Barcelona | Carlos Ferrer Ibáñez |
| 6 | Pau Llorens Vidal | València | (null) |
| 7 | Sofia Moreira Costa | Lisboa | (null) |
| 8 | Tiago Almeida Nunes | Porto | Sofia Moreira Costa |
| 9 | Camille Dubois | Lió | (null) |
| 10 | Julien Moreau | París | Camille Dubois |
| 11 | Elena Navarro Puig | Alacant | Pau Llorens Vidal |
| 12 | Diego Ramos Herrera | Sevilla | (null) |
| 13 | Núria Bosch Ferrer | Barcelona | Ana Belmonte Roca |
| 14 | Hugo Iglesias Pardo | Saragossa | (null) |
| 15 | Inés Carrasco Vega | València | Lucía Martínez Soler |
15 files: 8 clients referits i 7 sense referidor, exactament els recomptes que 01-06 anunciava.
La xarxa que dibuixen aquestes dades:
flowchart TD
L["Lucía (1)"] --> C2["Carlos (2)"]
L --> M["Marta (3)"]
L --> I["Inés (15)"]
C2 --> A["Ana (5)"]
A --> N["Núria (13)"]
S["Sofia (7)"] --> T["Tiago (8)"]
CD["Camille (9)"] --> J["Julien (10)"]
P["Pau (6)"] --> E["Elena (11)"]
JA["Javier (4)"]
D["Diego (12)"]
H["Hugo (14)"]
Qui va portar qui: el punt de vista del referidor
Màrqueting vol premiar els clients que més gent han portat. La consulta parteix del referidor:
SELECT ref.nom || ' ' || ref.cognoms AS referidor,
c.id AS client_id,
c.nom || ' ' || c.cognoms AS referit,
c.data_registre
FROM clients AS ref
JOIN clients AS c ON c.referit_per_id = ref.id
WHERE ref.id = 1
ORDER BY c.id;| referidor | client_id | referit | data_registre |
|---|---|---|---|
| Lucía Martínez Soler | 2 | Carlos Ferrer Ibáñez | 2025-01-22 |
| Lucía Martínez Soler | 3 | Marta Sanchis Gil | 2025-02-03 |
| Lucía Martínez Soler | 15 | Inés Carrasco Vega | 2026-01-08 |
Tres clients portats per la Lucía, la primera clienta de BotigaVerda. Al mòdul 4 comptarem aquests referits per persona amb GROUP BY per saber qui encapçala el programa; aquí ens quedem al detall.
Fixa't en un detall subtil d'escriptura: encara que l'àlies ref estigui escrit primer al FROM, continua sent la taula "pare" de la relació. Qui va primer és una decisió de llegibilitat, no de significat: el que fixa la direcció és la condició ON c.referit_per_id = ref.id.
SELF JOIN no jeràrquic: parelles dins del mateix grup
SELF JOIN no jeràrquic: parelles dins del mateix grupNo tot SELF JOIN recorre una relació reflexiva declarada. Un altre ús molt freqüent és emparellar files que comparteixen un atribut: productes de la mateixa categoria, empleats de la mateixa ciutat, comandes del mateix dia.
L'equip de màrqueting vol dissenyar lots de dos productes de la mateixa categoria. Primer intent:
Això retorna 78 files, i té dos problemes greus:
| Problema | Exemple |
|---|---|
| Emparella cada producte amb si mateix | (Crema facial, Crema facial): un "lot" d'un producte duplicat |
| Retorna cada parella dues vegades | (Crema, Xampú) i (Xampú, Crema) són la mateixa oferta |
La solució cap en tres caràcters: p1.id < p2.id.
-- ✅ CORRECTA
SELECT cat.nom AS categoria,
p1.nom AS producte_a,
p1.preu AS preu_a,
p2.nom AS producte_b,
p2.preu AS preu_b
FROM productes AS p1
JOIN productes AS p2 ON p1.categoria_id = p2.categoria_id
AND p1.id < p2.id
JOIN categories AS cat ON p1.categoria_id = cat.id
WHERE p1.categoria_id = 2
ORDER BY p1.id, p2.id;| categoria | producte_a | preu_a | producte_b | preu_b |
|---|---|---|---|---|
| Cosmètica natural | Crema facial d'àloe vera 50 ml | 18.90 | Xampú sòlid de romaní 80 g | 8.40 |
| Cosmètica natural | Crema facial d'àloe vera 50 ml | 18.90 | Oli corporal d'ametlles 200 ml | 14.25 |
| Cosmètica natural | Crema facial d'àloe vera 50 ml | 18.90 | Bàlsam labial de calèndula 15 ml | 4.60 |
| Cosmètica natural | Xampú sòlid de romaní 80 g | 8.40 | Oli corporal d'ametlles 200 ml | 14.25 |
| Cosmètica natural | Xampú sòlid de romaní 80 g | 8.40 | Bàlsam labial de calèndula 15 ml | 4.60 |
| Cosmètica natural | Oli corporal d'ametlles 200 ml | 14.25 | Bàlsam labial de calèndula 15 ml | 4.60 |
6 files: les 6 parelles possibles entre els 4 productes de Cosmètica natural. Sense el WHERE de retall, la consulta retorna 29 parelles a tot el catàleg.
Per què funciona p1.id < p2.id
Aquest truc mereix un paràgraf sencer, perquè és el patró canònic i es reutilitza en mil llocs.
Per a dos productes qualssevol A i B de la mateixa categoria, el producte cartesià genera quatre combinacions:
| Combinació | Compleix p1.id < p2.id? |
Què és |
|---|---|---|
| (A, A) | ❌ 5 < 5 és fals |
Producte amb si mateix |
| (B, B) | ❌ | Producte amb si mateix |
(A, B) amb id(A) < id(B) |
✅ | La parella, una sola vegada |
| (B, A) | ❌ id(B) < id(A) és fals |
La mateixa parella, duplicada |
La condició fa dues feines alhora amb un sol operador:
<en lloc de<=elimina els parells reflexius (A, A), perquè capidno és menor que si mateix.<en lloc de<>elimina el duplicat (B, A), perquè de les dues ordenacions possibles només una satisfà la desigualtat estricta.
L'aritmètica confirma l'efecte: amb n productes en una categoria, sense condició hi ha n² combinacions; amb <> n'hi ha n² − n; amb < n'hi ha exactament n(n−1)/2, que és el nombre de parelles de la combinatòria.
| Categoria | Productes | Sense condició (n²) |
Amb <> |
Amb < |
|---|---|---|---|---|
| Alimentació | 5 | 25 | 20 | 10 |
| Cosmètica natural | 4 | 16 | 12 | 6 |
| Llar sostenible | 4 | 16 | 12 | 6 |
| Begudes | 4 | 16 | 12 | 6 |
| Higiene personal | 2 | 4 | 2 | 1 |
| Complements | 1 | 1 | 0 | 0 |
| Total | 20 | 78 | 58 | 29 |
Nota: aquest és un
JOINamb una condició de desigualtat, el que a 03-01 anomenàvem non-equi join. La igualtatp1.categoria_id = p2.categoria_iddefineix el grup; la desigualtatp1.id < p2.idselecciona una de les dues ordenacions. És habitual que un non-equi join acompanyi un d'igualtat, no que el substitueixi.
- Empleats de la mateixa ciutat
El mateix patró, aplicat a empleats. RH vol organitzar dinars d'equip per oficina i necessita les parelles de companys que treballen a la mateixa ciutat:
SELECT e1.ciutat,
e1.nom AS empleat_a,
e1.carrec AS carrec_a,
e2.nom AS empleat_b,
e2.carrec AS carrec_b
FROM empleats AS e1
JOIN empleats AS e2 ON e1.ciutat = e2.ciutat
AND e1.id < e2.id
ORDER BY e1.id, e2.id
LIMIT 10;| ciutat | empleat_a | carrec_a | empleat_b | carrec_b |
|---|---|---|---|---|
| València | Rosa | Directora general | Andrés | Responsable de vendes |
| València | Rosa | Directora general | Beatriz | Responsable de logística |
| València | Rosa | Directora general | Óscar | Comercial |
| València | Rosa | Directora general | Marc | Atenció al client |
| València | Rosa | Directora general | Irene | Operària de magatzem |
| València | Rosa | Directora general | Daniel | Analista de dades |
| València | Andrés | Responsable de vendes | Beatriz | Responsable de logística |
| València | Andrés | Responsable de vendes | Óscar | Comercial |
| València | Andrés | Responsable de vendes | Marc | Atenció al client |
| València | Andrés | Responsable de vendes | Irene | Operària de magatzem |
(10 primeres de 21 files.)
21 parelles, totes de València: set dels vuit empleats hi treballen, i 7 × 6 / 2 = 21. Laia Puig Sanchis no apareix a cap fila, perquè és l'única que treballa a Castelló i no té amb qui emparellar-se.
Aquest detall és important: un SELF JOIN d'aquest tipus exclou automàticament els elements únics del seu grup. Si volguessis un llistat en què la Laia també aparegués (amb NULL com a companya), et caldria un LEFT JOIN amb la mateixa condició a l'ON.
També convé notar que aquí ciutat admet NULL. Si dos empleats tinguessin la ciutat sense omplir, NULL = NULL no és cert i no s'emparellarien, cosa que en aquest cas és el correcte: no sabem si treballen junts.
- Jerarquies de profunditat arbitrària
Un SELF JOIN recorre exactament un nivell de la jerarquia. Per pujar dos nivells calen dos SELF JOIN encadenats:
SELECT e.id,
e.nom AS empleat,
e.carrec,
cap.nom AS cap,
avi.nom AS cap_del_cap
FROM empleats AS e
LEFT JOIN empleats AS cap ON e.cap_id = cap.id
LEFT JOIN empleats AS avi ON cap.cap_id = avi.id
ORDER BY e.id;| id | empleat | carrec | cap | cap_del_cap |
|---|---|---|---|---|
| 1 | Rosa | Directora general | (null) | (null) |
| 2 | Andrés | Responsable de vendes | Rosa | (null) |
| 3 | Beatriz | Responsable de logística | Rosa | (null) |
| 4 | Óscar | Comercial | Andrés | Rosa |
| 5 | Laia | Comercial | Andrés | Rosa |
| 6 | Marc | Atenció al client | Andrés | Rosa |
| 7 | Irene | Operària de magatzem | Beatriz | Rosa |
| 8 | Daniel | Analista de dades | Rosa | (null) |
Funciona, i a BotigaVerda n'hi ha prou perquè l'organigrama té només tres nivells. Però fixa't en la limitació: el nombre de nivells està codificat a la consulta. Si l'empresa creix a cinc nivells, cal reescriure-la afegint-hi dos LEFT JOIN més; si un empleat està a set nivells de la direcció, aquesta consulta no l'assolirà mai.
Per a jerarquies de profunditat desconeguda cal una altra eina: les CTE recursives (
WITH RECURSIVE), que s'estudien a la lliçó 10-02. Amb elles es recorre un arbre de qualsevol profunditat amb una sola consulta, i es pot calcular el nivell de cada empleat o la cadena de comandament completa. El mateix val per a la xarxa de referits: "tots els clients que descendeixen de la Lucía, directament o indirectament" és una consulta recursiva, no unSELF JOIN.
CROSS JOIN: el producte cartesià deliberat
CROSS JOIN: el producte cartesià deliberatEl CROSS JOIN combina cada fila d'una taula amb cada fila de l'altra, sense cap condició. És el pas 1 del model mental de 03-01, sense el pas 2.
Retorna 6 × 20 = 120 files. Cap condició, cap ON: CROSS JOIN no admet ON, i si l'escrius dona error de sintaxi.
La mida creix com el producte dels cardinals, cosa que en taules reals és explosiva:
| Taula A | Taula B | Files resultants |
|---|---|---|
categories (6) |
proveidors (5) |
30 |
categories (6) |
12 mesos | 72 |
productes (20) |
clients (15) |
300 |
clients (15) |
productes (20) × 12 mesos |
3 600 |
linies_comanda (47) |
comandes (20) |
940 |
| 10 000 | 10 000 | 100 000 000 |
| 1 000 000 | 1 000 000 | 1 000 000 000 000 |
Aquesta última fila —un bilió de files— és la raó per la qual un CROSS JOIN accidental pot deixar sense memòria un servidor. La regla és simple: un CROSS JOIN només és acceptable quan almenys una de les dues taules és petita i de mida coneguda.
Les dues formes d'escriure'l
-- Forma explícita: recomanada
FROM categories AS cat CROSS JOIN proveidors AS pr
-- Forma amb coma: equivalent, però indistingible d'un oblit
FROM categories AS cat, proveidors AS prSón idèntiques per al motor. La primera és una declaració d'intencions; la segona és exactament el que apareix quan algú oblida la condició d'emparellament amb la sintaxi antiga de 03-01.
Convenció del curs: si vols un producte cartesià, escriu
CROSS JOINen majúscules i amb un comentari que expliqui per què. És la diferència entre "això està pensat" i "això és un descuit".
- Quan és útil i quan és un accident
| Situació | Deliberat? | Exemple |
|---|---|---|
| Generar totes les combinacions d'un informe perquè no hi hagi buits | ✅ Sí | Categoria × mes, perquè un mes sense vendes aparegui amb 0 |
| Construir una matriu de variants | ✅ Sí | Talles × colors d'un producte tèxtil |
| Multiplicar una fila de paràmetres contra una taula | ✅ Sí | Aplicar tres escenaris d'IVA a tot el catàleg |
| Generar dades de prova en volum | ✅ Sí | Creuar dues sèries numèriques per crear un milió de files |
Un JOIN al qual se li ha oblidat l'ON |
❌ No | El cas de 03-01: 20 × 15 = 300 files de brossa |
Una taula afegida al FROM sense relacionar-la |
❌ No | Afegir proveidors a una consulta i oblidar unir-la |
El cas útil per excel·lència: informes sense buits
Aquest és el motiu pel qual el CROSS JOIN existeix en el dia a dia d'un analista.
Imagina l'informe "vendes per categoria i mes del 2025". Si el construeixes només amb les vendes reals, els mesos sense vendes simplement no apareixen: una categoria que no va vendre res a l'agost no tindrà fila d'agost, i el gràfic saltarà de juliol a setembre com si l'agost no existís.
La solució professional consisteix a generar primer l'esquelet complet —totes les combinacions categoria × mes— i unir-hi després les vendes amb un LEFT JOIN:
flowchart LR
A["categories<br/>6 files"] --> C["CROSS JOIN<br/>72 combinacions"]
B["12 mesos<br/>generate_series"] --> C
C --> D["LEFT JOIN amb les vendes reals"]
D --> E["informe sense buits:<br/>els mesos sense vendes<br/>apareixen amb NULL"]
L'esquelet:
SELECT cat.id AS categoria_id,
cat.nom AS categoria,
m.mes
FROM categories AS cat
CROSS JOIN generate_series(1, 12) AS m(mes)
ORDER BY cat.id, m.mes;| categoria_id | categoria | mes |
|---|---|---|
| 1 | Alimentació | 1 |
| 1 | Alimentació | 2 |
| 1 | Alimentació | 3 |
| 1 | Alimentació | 4 |
| 1 | Alimentació | 5 |
| 1 | Alimentació | 6 |
| 1 | Alimentació | 7 |
| 1 | Alimentació | 8 |
(8 primeres de 72 files.)
72 files = 6 categories × 12 mesos. No falta cap combinació, i sobre aquesta base l'informe queda quadrat encara que una categoria no vengui res en tot l'any. La part de sumar les vendes és del mòdul 4; l'esquelet és d'aquí.
La matriu de variants
L'altre cas clàssic no necessita ni taules: es construeix amb llistes literals fent servir VALUES (que estudiaràs a fons a 05-02).
SELECT talla.t AS talla,
color.c AS color
FROM (VALUES ('S'), ('M'), ('L'), ('XL')) AS talla(t)
CROSS JOIN (VALUES ('blanc'), ('negre'), ('verd')) AS color(c)
ORDER BY talla.t, color.c;Retorna 12 files: les 4 talles × 3 colors d'un catàleg tèxtil. És la llista de referències que caldria donar d'alta. BotigaVerda no ven roba, però el patró apareix a qualsevol botiga amb variants de producte.
CROSS JOIN amb generate_series per a calendaris
CROSS JOIN amb generate_series per a calendarisgenerate_series és una funció generadora de files de PostgreSQL. Produeix una sèrie de valors i es fa servir al FROM com si fos una taula:
| generate_series |
|---|
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
Amb dates és on resulta més útil, perquè admet un interval com a pas:
SELECT m.mes::date
FROM generate_series(DATE '2025-01-01', DATE '2025-12-01', INTERVAL '1 month') AS m(mes);| mes |
|---|
| 2025-01-01 |
| 2025-02-01 |
| 2025-03-01 |
| 2025-04-01 |
| 2025-05-01 |
| 2025-06-01 |
| 2025-07-01 |
| 2025-08-01 |
| 2025-09-01 |
| 2025-10-01 |
| 2025-11-01 |
| 2025-12-01 |
I creuada amb categories genera el calendari complet de l'informe anterior, ara amb dates reals:
SELECT cat.nom AS categoria,
m.mes::date AS mes
FROM categories AS cat
CROSS JOIN generate_series(DATE '2025-01-01', DATE '2025-12-01', INTERVAL '1 month') AS m(mes)
ORDER BY cat.id, m.mes;72 files, a punt per rebre les vendes amb un LEFT JOIN.
Nota de dialecte:
generate_seriesés específic de PostgreSQL. Els equivalents en altres motors:
Motor Com generar una sèrie PostgreSQL generate_series(inici, fi [, pas])SQL Server GENERATE_SERIES(des del 2022); abans, una CTE recursiva o una taula de calendariOracle CONNECT BY LEVEL <= nMySQL 8+ CTE recursiva WITH RECURSIVESQLite CTE recursiva, o l'extensió generate_seriesL'alternativa portable en qualsevol motor és mantenir una taula de calendari permanent amb una fila per dia o per mes. És el que fan la majoria dels magatzems de dades, i evita dependre del dialecte.
LATERAL, de passada
Existeix una variant avançada del CROSS JOIN en què la taula de la dreta pot referir-se a columnes de l'esquerra:
Es diu CROSS JOIN LATERAL (o LEFT JOIN LATERAL) i serveix, per exemple, per a "les tres ressenyes més recents de cada producte". Necessita subconsultes, que són el mòdul 7, així que aquí només ho esmentem perquè reconeguis la paraula si la veus. LATERAL és estàndard SQL i està disponible a PostgreSQL, Oracle i SQL Server (on es diu CROSS APPLY / OUTER APPLY).
Errors habituals i consells
- Oblidar els àlies en un
SELF JOIN.ERROR: table name "empleats" specified more than once. Cada còpia necessita el seu propi nom. - Fer servir
INNER JOINen una jerarquia. Perds l'arrel de l'arbre: Rosa Alcázar Vives desapareix de l'organigrama perquè el seucap_idésNULL. Fes servirLEFT JOIN. - Oblidar la condició
p1.id < p2.iden generar parelles. Obtens cada element emparellat amb si mateix i cada parella per duplicat: 78 files en comptes de 29. - Fer servir
<>en lloc de<. Elimina els parells reflexius però no els duplicats: 58 files en comptes de 29. - Creure que un
SELF JOINrecorre tota la jerarquia. En recorre exactament un nivell. Dos nivells, dosJOIN. Profunditat desconeguda,WITH RECURSIVE(10-02). - Emparellar per una columna que admet
NULLen unSELF JOINde grup. Dos empleats ambciutataNULLno s'emparellen, perquèNULL = NULLno és cert. - Escriure un
CROSS JOINamb la sintaxi de comes. És correcte, però indistingible d'un oblit. EscriuCROSS JOINexplícit. - Intentar posar
ONen unCROSS JOIN. No l'admet: si necessites condició, no és unCROSS JOIN, és unINNER JOIN. - Creuar dues taules grans "per veure què surt". Comprova abans el producte dels seus recomptes. 10 000 × 10 000 són cent milions de files.
- Consell: dibuixa l'arbre abans d'escriure el
SELF JOIN. Saber qui és pare i qui és fill evita invertir la condicióON, que és l'error més comú i el més difícil de veure. - Consell: compta les parelles esperades abans d'executar.
n(n−1)/2per a les parelles d'un grup den. Si el resultat no coincideix, saps immediatament que falta la condició o que en sobra. - Consell: per a informes per període, construeix sempre primer l'esquelet.
CROSS JOINde dimensions +LEFT JOINdels fets. És la forma estàndard que no hi hagi buits, i la faràs servir tan bon punt comencis a agregar al mòdul 4.
Exercicis
Exercici 1
Màrqueting vol una fitxa del programa de referits que mostri, per a cada client: el seu nom complet, qui el va referir (o NULL si va arribar pel seu compte) i qui va referir el seu referidor (l'"avi" de la xarxa).
- Escriu la consulta fent servir dos
SELF JOINencadenats sobreclients. - Restringeix-la als clients que sí que tenen referidor i comenta el resultat.
- Per què aquesta consulta no pot respondre a "tots els clients que descendeixen de la Lucía, a qualsevol profunditat"?
Exercici 2
Compres vol llançar lots de dos productes de la mateixa categoria el preu conjunt dels quals no superi els 10 €, fent servir només productes actius. Escriu la consulta que retorni la categoria, els dos productes amb els seus preus i el preu del lot, ordenada per preu del lot.
Explica quin paper fa cadascuna de les condicions de l'ON i del WHERE.
Exercici 3
Respon raonant, sense executar:
- Quantes files retorna
SELECT * FROM empleats AS e1 CROSS JOIN empleats AS e2;? - I
SELECT * FROM empleats AS e1 JOIN empleats AS e2 ON e1.id <> e2.id;? - I
SELECT * FROM empleats AS e1 JOIN empleats AS e2 ON e1.id < e2.id;? - Escriu l'esquelet d'un informe proveïdor × trimestre del 2025 fent servir
CROSS JOINigenerate_series. Quantes files té?
Solucions
Solució 1
1. Amb dos SELF JOIN encadenats:
SELECT c.id,
c.nom || ' ' || c.cognoms AS client,
ref.nom AS referidor,
ref2.nom AS referidor_del_referidor
FROM clients AS c
LEFT JOIN clients AS ref ON c.referit_per_id = ref.id
LEFT JOIN clients AS ref2 ON ref.referit_per_id = ref2.id
ORDER BY c.id;Retorna les 15 files de clients, amb dues columnes que van quedant a NULL a mesura que s'esgota la cadena.
2. Només els clients amb referidor (n'hi ha prou de canviar el primer LEFT JOIN per un INNER JOIN; el segon ha de continuar sent LEFT):
SELECT c.nom || ' ' || c.cognoms AS client,
ref.nom AS referidor,
ref2.nom AS referidor_del_referidor
FROM clients AS c
INNER JOIN clients AS ref ON c.referit_per_id = ref.id
LEFT JOIN clients AS ref2 ON ref.referit_per_id = ref2.id
ORDER BY c.id;| client | referidor | referidor_del_referidor |
|---|---|---|
| Carlos Ferrer Ibáñez | Lucía | (null) |
| Marta Sanchis Gil | Lucía | (null) |
| Ana Belmonte Roca | Carlos | Lucía |
| Tiago Almeida Nunes | Sofia | (null) |
| Julien Moreau | Camille | (null) |
| Elena Navarro Puig | Pau | (null) |
| Núria Bosch Ferrer | Ana | Carlos |
| Inés Carrasco Vega | Lucía | (null) |
8 files. Només dos clients tenen "avi" a la xarxa: l'Ana (portada pel Carlos, que al seu torn va venir de la Lucía) i la Núria (portada per l'Ana, que va venir del Carlos). La resta descendeix directament d'algú que va arribar pel seu compte.
Fixa't que la cadena Lucía → Carlos → Ana → Núria té tres salts, i aquesta consulta només n'arriba a veure dos: a la fila de la Núria apareix el Carlos com a avi, però la Lucía —la besàvia— ja no hi cap.
3. Per què no serveix per a "tots els descendents de la Lucía": perquè el nombre de nivells està escrit a la consulta. Cada nivell addicional exigeix un LEFT JOIN més, i per respondre aquesta pregunta caldria conèixer per endavant la profunditat màxima de la xarxa. Amb una cadena de set referits, caldrien set JOIN. L'eina correcta és una CTE recursiva (WITH RECURSIVE), que recorre l'arbre fins a esgotar-lo amb una sola consulta: lliçó 10-02.
Solució 2
SELECT cat.nom AS categoria,
p1.nom AS producte_a,
p1.preu AS preu_a,
p2.nom AS producte_b,
p2.preu AS preu_b,
ROUND(p1.preu + p2.preu, 2) AS preu_lot
FROM productes AS p1
JOIN productes AS p2 ON p1.categoria_id = p2.categoria_id
AND p1.id < p2.id
JOIN categories AS cat ON p1.categoria_id = cat.id
WHERE p1.actiu
AND p2.actiu
AND p1.preu + p2.preu <= 10
ORDER BY p1.preu + p2.preu, p1.id, p2.id;| categoria | producte_a | preu_a | producte_b | preu_b | preu_lot |
|---|---|---|---|---|---|
| Alimentació | Pasta d'espelta 500 g | 2.80 | Tomàquet triturat ecològic 400 g | 1.95 | 4.75 |
| Alimentació | Arròs integral ecològic 1 kg | 3.90 | Tomàquet triturat ecològic 400 g | 1.95 | 5.85 |
| Alimentació | Arròs integral ecològic 1 kg | 3.90 | Pasta d'espelta 500 g | 2.80 | 6.70 |
| Begudes | Infusió de camamilla ecològica 20 u | 3.25 | Kombutxa de gingebre 750 ml | 4.95 | 8.20 |
| Begudes | Infusió de camamilla ecològica 20 u | 3.25 | Suc de taronja premsat en fred 1 L | 5.40 | 8.65 |
5 lots possibles, tots d'Alimentació i Begudes: són les dues categories amb productes barats. A Cosmètica natural, el lot més econòmic seria el bàlsam (4,60 €) amb el xampú (8,40 €), que ja suma 13 €.
Paper de cada condició:
| Condició | On | Paper |
|---|---|---|
p1.categoria_id = p2.categoria_id |
ON |
Emparellament: defineix el grup dins del qual es formen parelles |
p1.id < p2.id |
ON |
Emparellament: evita el parell reflexiu i el duplicat invertit |
p1.categoria_id = cat.id |
ON |
Emparellament: porta el nom de la categoria |
p1.actiu AND p2.actiu |
WHERE |
Filtratge: descarta productes descatalogats. Exclou les Càpsules d'espirulina, únic producte amb actiu = false |
p1.preu + p2.preu <= 10 |
WHERE |
Filtratge: la regla de negoci del preu del lot |
Les condicions d'emparellament van a l'ON i les de filtratge al WHERE, seguint la convenció de 03-01. Com que tots els JOIN són INNER, aquí seria equivalent posar-les a qualsevol dels dos llocs (03-02), però la separació manté la consulta llegible.
Solució 3
1. CROSS JOIN d'empleats amb si mateixa: 8 × 8 = 64 files. Totes les combinacions, inclosos els vuit parells de cada empleat amb si mateix.
2. Amb ON e1.id <> e2.id: 56 files. S'eliminen els 8 parells reflexius (64 − 8), però cada parella continua apareixent dues vegades, en els dos ordres.
3. Amb ON e1.id < e2.id: 28 files. És 8 × 7 / 2, el nombre de parelles diferents de 8 elements. Cada parella una sola vegada i cap amb si mateixa.
La progressió 64 → 56 → 28 resumeix tota la secció 4.
4. Esquelet proveïdor × trimestre del 2025:
SELECT pr.id AS proveidor_id,
pr.nom AS proveidor,
t.trimestre::date AS inici_trimestre
FROM proveidors AS pr
CROSS JOIN generate_series(DATE '2025-01-01', DATE '2025-10-01', INTERVAL '3 months') AS t(trimestre)
ORDER BY pr.id, t.trimestre;20 files = 5 proveïdors × 4 trimestres. Els trimestres generats comencen l'1 de gener, l'1 d'abril, l'1 de juliol i l'1 d'octubre del 2025; el límit superior és 2025-10-01 perquè generate_series inclou l'extrem final, i posar-hi 2025-12-01 produiria un cinquè valor no desitjat.
Sobre aquest esquelet, un LEFT JOIN amb les compres reals donaria l'informe trimestral per proveïdor sense buits, inclòs el proveïdor 5 (EcoNordic Supplies, inactiu), que apareixeria amb tots els seus trimestres a NULL.
Conclusió
Tanques els dos JOIN especials:
- Un
SELF JOINés unJOINnormal en què la mateixa taula apareix dues vegades. Els àlies són obligatoris —ERROR: table name specified more than once— i convé nomenar-los pel paper que fan:e/cap,c/ref,p1/p2. - La jerarquia d'
empleatses recorre ambe.cap_id = cap.id. AmbINNER JOINes perd l'arrel (7 files, sense la Rosa); ambLEFT JOINapareix l'equip complet (8 files). En tota jerarquia, l'arrel té la FK aNULL. - La xarxa de referits de
clientsfunciona igual: 8 clients referits i 7 que van arribar pel seu compte. Lucía Martínez Soler en va portar tres. - Per a parelles dins d'un mateix grup —productes de la mateixa categoria, empleats de la mateixa ciutat— el patró canònic és
ON a.grup = b.grup AND a.id < b.id. El<estricte elimina d'un cop els parells reflexius i els duplicats invertits: 29 parelles en comptes de 78. - Un
SELF JOINrecorre un nivell perJOIN. Per a profunditat arbitrària calen CTE recursives (10-02). - El
CROSS JOINés el producte cartesià explícit, senseON. És un accident quan s'oblida la condició d'emparellament, i una eina quan genera deliberadament l'esquelet d'un informe (categoria × mes) o una matriu de variants (talla × color). generate_seriesde PostgreSQL produeix sèries numèriques i de dates utilitzables alFROM. Creuat amb una dimensió, dona calendaris complets sense buits; en altres motors se substitueix per CTE recursives o per una taula de calendari.
Només queda una forma de combinar taules que encara no coneixes. Tots els JOIN d'aquest mòdul afegeixen columnes: agafen una fila d'aquí, una d'allà i les enganxen de costat. A la lliçó següent, UNION, INTERSECT i EXCEPT, faràs el contrari: apilar resultats sencers en vertical, afegint files en lloc de columnes. Construiràs una llista unificada de contactes a partir de clients, empleats i proveidors; esbrinaràs a quines ciutats hi ha alhora clients i empleats; i tornaràs a trobar els tres productes mai venuts, aquesta vegada sense cap JOIN.
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
