Tot el que has fet en aquest mòdul consisteix a emparellar files en horitzontal: agafar una fila de comandes, buscar-li la parella a clients i enganxar-les de costat per obtenir una fila més ampla. Els JOIN afegeixen columnes.
Existeix una segona forma de combinar, completament diferent: apilar resultats en vertical. S'executen dues consultes independents, cadascuna amb les seves pròpies columnes, i les seves files s'ajunten una sota l'altra. Els operadors de conjunts —UNION, INTERSECT i EXCEPT— afegeixen files.
Aquesta lliçó tanca el mòdul 3. Amb ella tindràs les dues maneres de combinar informació en SQL i sabràs quina demanar en cada cas.
Contingut
JOINenfront dels operadors de conjunts- Les regles de compatibilitat
UNIONiUNION ALL- La llista unificada de contactes
INTERSECT: el que és a tots dosEXCEPT: el que és al primer i no al segonEXCEPTenfront de l'anti-join de 03-03INTERSECT ALLiEXCEPT ALL- Precedència i parèntesis
ORDER BYiLIMITsobre el resultat combinat- Suport per motor
- Errors habituals i consells
- Exercicis
- Conclusió
JOIN enfront dels operadors de conjunts
JOIN enfront dels operadors de conjuntsflowchart TB
subgraph J["JOIN — combina en HORITZONTAL"]
direction LR
J1["fila d'A<br/>(3 columnes)"] --- J2["fila de B<br/>(4 columnes)"]
J2 --- J3["→ una fila<br/>de 7 columnes"]
end
subgraph U["UNION — combina en VERTICAL"]
direction TB
U1["files d'A<br/>(3 columnes)"]
U2["files de B<br/>(3 columnes)"]
U1 --- U2
U2 --- U3["→ més files,<br/>sempre 3 columnes"]
end
JOIN |
Operadors de conjunts | |
|---|---|---|
| Què fa | Emparella files de dues taules | Apila els resultats de dues consultes |
| Efecte sobre el resultat | Més columnes | Més files |
| Relació entre les taules | Necessita una condició d'emparellament (ON) |
Cap: les consultes són independents |
| Requisit | Que existeixi un camí entre les taules | Mateix nombre de columnes i tipus compatibles |
| Exemple | "Cada comanda amb el nom del seu client" | "Tots els contactes: clients, empleats i proveïdors" |
La diferència clau és la segona meitat de la tercera fila: els operadors de conjunts no necessiten que les taules estiguin relacionades. Pots unir els resultats de dues consultes sobre taules que no comparteixen ni una clau forana, sempre que les seves columnes encaixin.
- Les regles de compatibilitat
Perquè dues consultes es puguin combinar hi ha tres regles, i totes tres són estrictes:
1. Mateix nombre de columnes.
2. Tipus compatibles, columna a columna, en el mateix ordre.
La columna 1 de la primera consulta es combina amb la columna 1 de la segona, la 2 amb la 2, etc. Els noms no importen; importa la posició. PostgreSQL aplica les seves regles de conversió implícita: INTEGER i NUMERIC es combinen sense problema, VARCHAR i TEXT també, però DATE i VARCHAR no.
3. Els noms de columna del resultat els posa la primera consulta.
La columna del resultat es diu contacte. L'àlies de la segona consulta s'ignora completament, i aquesta és una font clàssica de confusió en llegir codi aliè.
Conseqüència pràctica: escriu els àlies a la primera consulta i no et molestis a repetir-los a les altres (encara que fer-ho ajuda a documentar què és cada columna).
El truc de les columnes que falten
Què passa si una taula no té una columna que l'altra sí que té? A BotigaVerda, clients i proveidors tenen email, però empleats no. La solució és omplir el buit amb un literal:
El ::varchar és un cast explícit. Sense ell, PostgreSQL de vegades pot inferir el tipus del NULL a partir de l'altra branca, però no sempre; escriure'l evita l'error failed to determine data type of column. Els casts s'estudien a fons a la lliçó 06-04.
UNION i UNION ALL
UNION i UNION ALLUNION apila les files de dues consultes i elimina els duplicats. UNION ALL les apila i no elimina res.
Vegem-ho amb les ciutats on BotigaVerda té presència:
| ciutat |
|---|
| Alacant |
| Barcelona |
| Castelló |
| Lió |
| Lisboa |
| Madrid |
| París |
| Porto |
| Saragossa |
| Sevilla |
| València |
11 files. I la mateixa consulta amb UNION ALL retorna 23 files: les 15 ciutats de clients (amb València repetida quatre vegades i Barcelona dues) més les 8 d'empleats (amb València set vegades).
| Operador | Files aquí | Què fa | Cost |
|---|---|---|---|
UNION ALL |
23 | Concatena, sense més | Barat: només llegeix i emet |
UNION |
11 | Concatena i dedupla | Car: necessita ordenar o construir una taula hash |
Per què UNION ALL sol ser el que es vol
UNION sense ALL fa una feina extra que moltes vegades no només és innecessària, sinó incorrecta:
- És més lent. Deduplicar exigeix ordenar tot el resultat o mantenir una estructura hash a la memòria. Amb milions de files, la diferència és enorme.
- Pot esborrar files legítimes. Si dos clients diferents es diguessin igual i visquessin a la mateixa ciutat,
SELECT nom, ciutat FROM clients UNION ...els col·lapsaria en un. Hauries perdut un client sense assabentar-te'n. - Compara la fila sencera. Dues files són duplicades només si totes les seves columnes coincideixen. Afegir una columna
idalSELECTfa queUNIONdeixi d'eliminar res, i llavors només n'estàs pagant el cost.
Regla del curs: fes servir
UNION ALLper defecte. Recorre aUNIONúnicament quan l'eliminació de duplicats sigui l'objectiu explícit de la consulta, com al llistat de ciutats de dalt. És la mateixa filosofia que la deDISTINCTa 02-04: si el necessites per "arreglar" un resultat, revisa abans si la consulta està ben plantejada.
Aquesta és també la raó per la qual l'emulació del FULL OUTER JOIN a MySQL (03-05) fa servir UNION i no UNION ALL: allà les dues meitats sí que produeixen les mateixes files coincidents, i cal eliminar-les.
- La llista unificada de contactes
Un cas real: l'empresa vol una agenda única amb totes les persones i entitats amb què tracta, assenyalant d'on surt cadascuna. Els tres orígens viuen en taules diferents i sense cap relació entre si: és l'escenari perfecte per a UNION ALL.
SELECT 'client' AS origen,
c.nom || ' ' || c.cognoms AS nom,
c.email,
c.ciutat
FROM clients AS c
UNION ALL
SELECT 'empleat',
e.nom || ' ' || e.cognoms,
NULL::varchar,
e.ciutat
FROM empleats AS e
UNION ALL
SELECT 'proveidor',
pr.nom,
pr.email,
NULL::varchar
FROM proveidors AS pr
ORDER BY origen, nom;| origen | nom | ciutat | |
|---|---|---|---|
| client | Ana Belmonte Roca | [email protected] | Barcelona |
| client | Camille Dubois | [email protected] | Lió |
| client | Carlos Ferrer Ibáñez | [email protected] | València |
| client | Diego Ramos Herrera | [email protected] | Sevilla |
| client | Elena Navarro Puig | [email protected] | Alacant |
| client | Hugo Iglesias Pardo | [email protected] | Saragossa |
| client | Inés Carrasco Vega | [email protected] | València |
| client | Javier Ortega Ruiz | [email protected] | Madrid |
| client | Julien Moreau | [email protected] | París |
| client | Lucía Martínez Soler | [email protected] | València |
| client | Marta Sanchis Gil | [email protected] | Castelló |
| client | Núria Bosch Ferrer | [email protected] | Barcelona |
| client | Pau Llorens Vidal | [email protected] | València |
| client | Sofia Moreira Costa | [email protected] | Lisboa |
| client | Tiago Almeida Nunes | [email protected] | Porto |
| empleat | Andrés Company Talens | (null) | València |
| empleat | Beatriz Nadal Ripoll | (null) | València |
| empleat | Daniel Vercher Lluch | (null) | València |
| empleat | Irene Salvador Mira | (null) | València |
| empleat | Laia Puig Sanchis | (null) | Castelló |
| empleat | Marc Estévez Roig | (null) | València |
| empleat | Óscar Peris Blasco | (null) | València |
| empleat | Rosa Alcázar Vives | (null) | València |
| proveidor | BioSierra Ibérica | [email protected] | (null) |
| proveidor | EcoNordic Supplies | [email protected] | (null) |
| proveidor | Huerta del Turia | [email protected] | (null) |
| proveidor | Maison Nature | [email protected] | (null) |
| proveidor | Verde Atlántico | [email protected] | (null) |
28 files = 15 clients + 8 empleats + 5 proveïdors.
Quatre decisions de disseny d'aquesta consulta mereixen comentari:
| Decisió | Per què |
|---|---|
Columna literal 'client' |
Sense ella, el resultat seria una llista de noms sense saber d'on surt cadascun. Una columna d'origen és imprescindible en qualsevol UNION de fonts heterogènies |
UNION ALL i no UNION |
Un client i un empleat es podrien dir igual; amb UNION un dels dos desapareixeria. A més, la columna origen els fa diferents de tota manera, així que UNION només costaria temps |
NULL::varchar per a les columnes absents |
empleats no té email i proveidors no té ciutat. El buit s'omple amb un nul del tipus correcte |
ORDER BY al final, una sola vegada |
Ordena el resultat combinat, no cada consulta per separat. Vegeu la secció 10 |
Fixa't també en l'ordre alfabètic: "Óscar" apareix entre "Marc" i "Rosa" perquè la base de dades fa servir la col·lació ca-ES-x-icu que vas configurar a 02-05. Amb la col·lació per defecte C, "Óscar" aniria al final del bloc, després de "Rosa".
INTERSECT: el que és a tots dos
INTERSECT: el que és a tots dosINTERSECT retorna les files que apareixen a totes dues consultes.
flowchart LR
subgraph R[" "]
direction LR
A(("només a A<br/>❌"))
I(("a A i a B<br/>✅"))
B(("només a B<br/>❌"))
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
A quines ciutats tenim alhora clients i empleats?
| ciutat |
|---|
| Castelló |
| València |
Dues ciutats. València, on hi ha la seu i viuen quatre clients, i Castelló, on treballa Laia Puig Sanchis i viu Marta Sanchis Gil. És una consulta amb lectura de negoci immediata: són les ciutats on es podria organitzar un lliurament en mà o un acte amb clients.
A quins països tenim alhora clients i proveïdors?
| pais |
|---|
| Espanya |
| França |
| Portugal |
Tres països. Alemanya en queda fora perquè allà hi ha proveïdor (EcoNordic Supplies) però cap client.
Dues propietats d'INTERSECT que convé saber:
- Elimina duplicats per defecte, igual que
UNION. Si una ciutat aparegués cinc vegades aclientsi tres aempleats, el resultat la mostra una vegada. - És commutatiu:
A INTERSECT BiB INTERSECT Adonen el mateix. És la propietat queEXCEPTno té.
EXCEPT: el que és al primer i no al segon
EXCEPT: el que és al primer i no al segonEXCEPT retorna les files de la primera consulta que no apareixen a la segona. És la diferència de conjunts.
flowchart LR
subgraph R[" "]
direction LR
A(("només a A<br/>✅"))
I(("a A i a B<br/>❌"))
B(("només a B<br/>❌"))
end
style A fill:#d9f0d9,stroke:#2b7a2b,stroke-width:3px
style I fill:#f8f8f8,stroke:#bbb,stroke-dasharray: 4 4
style B fill:#f8f8f8,stroke:#bbb,stroke-dasharray: 4 4
Quins productes no s'han venut mai? Els identificadors del catàleg, menys els identificadors que apareixen a les vendes:
| id |
|---|
| 13 |
| 19 |
| 20 |
Els tres de sempre. La consulta es llegeix gairebé com la pregunta: "tots els productes, llevant els que s'han venut".
A quins països tenim proveïdor però cap client?
| pais |
|---|
| Alemanya |
Una fila. I ara la clau d'EXCEPT: no és commutatiu. Dona-hi la volta:
Zero files: no hi ha cap país amb clients en què no tinguem també proveïdor. Les dues consultes responen a preguntes diferents, i confondre-les és l'error més freqüent amb aquest operador.
| Consulta | Significat | Resultat |
|---|---|---|
proveidors EXCEPT clients |
Països on comprem però no venem | Alemanya |
clients EXCEPT proveidors |
Països on venem però no comprem | (cap) |
Nota de dialecte: a Oracle aquest operador es diu
MINUS, noEXCEPT. Des d'Oracle 21c també s'admetEXCEPTcom a sinònim, però trobaràsMINUSen tot el codi anterior. El comportament és idèntic.
EXCEPT enfront de l'anti-join de 03-03
EXCEPT enfront de l'anti-join de 03-03Tens ara dues formes de respondre a "quins productes no s'han venut mai?". Compara-les:
-- Opció A: anti-join (03-03)
SELECT p.id, p.nom, p.preu, p.stock, p.actiu
FROM productes AS p
LEFT JOIN linies_comanda AS lc ON lc.producte_id = p.id
WHERE lc.id IS NULL
ORDER BY p.id;-- Opció B: EXCEPT
SELECT id FROM productes
EXCEPT
SELECT producte_id FROM linies_comanda
ORDER BY id;Totes dues identifiquen els productes 13, 19 i 20. Però no són intercanviables:
| Aspecte | Anti-join | EXCEPT |
|---|---|---|
| Columnes que pot retornar | Totes les de productes: nom, preu, stock, actiu... |
Només les que es comparen. Si afegeixes nom al primer SELECT, cal afegir alguna cosa comparable al segon, i linies_comanda no té nom de producte |
| Llegibilitat de la intenció | Requereix entendre per què funciona l'IS NULL |
Es llegeix com la frase: "els productes, menys els venuts" |
| Duplicats | Els conserva | Els elimina sempre |
| Ús típic | Informes: necessites les dades de les files trobades | Comprovacions i conciliacions: en tens prou amb la llista de claus |
| Rendiment | Excel·lent amb índex sobre la FK | Requereix deduplicar tots dos costats; sol ser una mica més car |
Quan triar cadascun: si necessites dades de les files resultants, fes servir l'anti-join. Si només necessites la llista d'identificadors —per a un recompte, una comprovació d'integritat, un informe de conciliació—,
EXCEPTés més curt i es llegeix millor.
Al mòdul 7 apareixerà una tercera forma, NOT EXISTS amb una subconsulta correlacionada, que combina el millor de totes dues: retorna la fila sencera i es llegeix com la pregunta. I una quarta, NOT IN, que sembla la més natural i té un comportament traïdor amb els nuls. Les quatre es comparen a la lliçó 07-05.
INTERSECT ALL i EXCEPT ALL
INTERSECT ALL i EXCEPT ALLIgual que UNION té la seva variant ALL, INTERSECT i EXCEPT també la tenen. La seva semàntica és multiconjunt: en lloc de treballar amb presència o absència, compten quantes vegades apareix cada fila.
| Operador | Si una fila apareix m vegades a A i n vegades a B, en surt... |
|---|---|
INTERSECT |
1 vegada (si m ≥ 1 i n ≥ 1) |
INTERSECT ALL |
min(m, n) vegades |
EXCEPT |
1 vegada (si m ≥ 1 i n = 0) |
EXCEPT ALL |
max(m − n, 0) vegades |
Exemple sobre les ciutats: València apareix 4 vegades a clients i 7 vegades a empleats.
Retorna València 4 vegades —min(4, 7)— i Castelló 1 vegada —min(1, 1)—: 5 files en total. Amb INTERSECT a seques en serien 2.
Retorna València 3 vegades —max(7 − 4, 0)— i res més: max(1 − 1, 0) = 0 per a Castelló.
A la pràctica es fan servir molt poc. El seu terreny són les comprovacions de qualitat de dades del tipus "aquesta migració ha duplicat files?", on importa el nombre de repeticions i no només la seva presència. És útil saber que existeixen; no és habitual escriure-les.
Nota de dialecte:
INTERSECT ALLiEXCEPT ALLsón estàndard i són a PostgreSQL, però no a SQL Server ni a Oracle (onMINUSno té variantALL).
- Precedència i parèntesis
Quan s'encadenen tres o més consultes, l'ordre d'avaluació importa. L'estàndard SQL estableix que:
INTERSECTté més precedència queUNIONiEXCEPT, que s'avaluen d'esquerra a dreta entre si.
És a dir, A UNION B INTERSECT C significa A UNION (B INTERSECT C), igual que 2 + 3 * 4 significa 2 + (3 * 4).
Vegem-ho amb un cas que canvia radicalment segons com s'agrupi. La consulta:
SELECT id FROM productes
EXCEPT
SELECT producte_id FROM linies_comanda
INTERSECT
SELECT producte_id FROM ressenyes
ORDER BY id;PostgreSQL l'avalua com a productes EXCEPT (linies_comanda INTERSECT ressenyes):
linies_comanda INTERSECT ressenyes= els productes que s'han venut i tenen ressenya = 9 productes (1, 2, 5, 6, 10, 12, 15, 16, 18).productes EXCEPT aquests 9= 11 productes: 3, 4, 7, 8, 9, 11, 13, 14, 17, 19 i 20.
| id |
|---|
| 3 |
| 4 |
| 7 |
| 8 |
| 9 |
| 11 |
| 13 |
| 14 |
| 17 |
| 19 |
| 20 |
Són exactament els 11 productes sense cap ressenya. Ara forcem l'altra agrupació amb parèntesis:
(SELECT id FROM productes
EXCEPT
SELECT producte_id FROM linies_comanda)
INTERSECT
SELECT producte_id FROM ressenyes;Zero files, perquè (productes EXCEPT linies_comanda) són els tres productes mai venuts (13, 19, 20) i cap d'ells no té ressenya. La mateixa consulta, dues agrupacions, 11 files contra 0.
Regla del curs: tan bon punt hi hagi tres o més consultes encadenades amb operadors diferents, fes servir parèntesis sempre, encara que coincideixin amb la precedència per defecte. Costen dos caràcters i eliminen tota ambigüitat per a qui llegeixi el codi.
Nota de dialecte important: SQLite no implementa aquesta precedència. Avalua els operadors compostos estrictament d'esquerra a dreta, així que la primera consulta d'aquesta secció retornaria 0 files a SQLite i 11 a PostgreSQL, MySQL, SQL Server i Oracle. És un argument definitiu a favor dels parèntesis explícits: amb ells, la consulta significa el mateix a tots els motors.
ORDER BY i LIMIT sobre el resultat combinat
ORDER BY i LIMIT sobre el resultat combinatORDER BY i LIMIT no pertanyen a cap de les consultes: s'apliquen al resultat combinat i van al final de tot.
| ciutat |
|---|
| Alacant |
| Barcelona |
| Castelló |
| Lió |
| Lisboa |
Les cinc primeres ciutats per ordre alfabètic del conjunt ja unificat. Detalls importants:
| Detall | Explicació |
|---|---|
| Els noms vàlids són els de la primera consulta | Si la primera columna es diu contacte, escriu ORDER BY contacte, encara que a la segona consulta la columna es digui d'una altra manera |
| Es pot ordenar per posició | ORDER BY 1 ordena per la primera columna. És especialment còmode aquí, on els noms poden ser confusos (02-05) |
LIMIT retalla el total, no cada meitat |
LIMIT 5 sobre un UNION de dues consultes de 15 i 8 files retorna 5 files en total |
| Continua valent la regla de 02-06 | Sense ORDER BY, l'ordre del resultat combinat no està garantit, ni tan sols el de "primer A i després B" |
Si necessites ordenar o limitar una de les consultes per separat, cal tancar-la entre parèntesis:
(SELECT ciutat FROM clients ORDER BY ciutat LIMIT 3)
UNION ALL
(SELECT ciutat FROM empleats ORDER BY ciutat LIMIT 3);És sintaxi vàlida a PostgreSQL, encara que poc freqüent: gairebé sempre el que es vol és ordenar el total.
A l'ordre lògic d'execució, el pas queda així: cada consulta es resol completament (amb el seu FROM, els seus JOIN, el seu WHERE i el seu SELECT), després s'aplica l'operador de conjunt, i només llavors s'ordena i es retalla.
flowchart TD
A["consulta 1<br/>FROM → WHERE → SELECT"] --> C["operador de conjunt<br/>UNION / INTERSECT / EXCEPT"]
B["consulta 2<br/>FROM → WHERE → SELECT"] --> C
C --> D["ORDER BY<br/>sobre el resultat combinat"]
D --> E["LIMIT / OFFSET"]
- Suport per motor
| Motor | UNION / UNION ALL |
INTERSECT |
EXCEPT |
Variants ALL |
|---|---|---|---|---|
| PostgreSQL | ✅ | ✅ | ✅ | ✅ INTERSECT ALL, EXCEPT ALL |
| MySQL / MariaDB | ✅ | ✅ des de MySQL 8.0.31 (2022) | ✅ des de 8.0.31 | ✅ des de 8.0.31 |
| SQLite | ✅ | ✅ | ✅ | ❌ Sense variants ALL; a més, precedència d'esquerra a dreta |
| SQL Server | ✅ | ✅ | ✅ | ❌ |
| Oracle | ✅ | ✅ | ✅ com a MINUS (i EXCEPT des de 21c) |
❌ |
UNION és l'únic dels tres que pots donar per segur en qualsevol motor i qualsevol versió. Si escrius SQL portable i necessites INTERSECT o EXCEPT sobre MySQL antic, l'alternativa és un JOIN (per a la intersecció) o un anti-join (per a la diferència), que és justament el que feia tothom abans del 2022.
Errors habituals i consells
- Diferent nombre de columnes.
ERROR: each UNION query must have the same number of columns. Compta les columnes de cada branca abans d'executar. - Tipus incompatibles a la mateixa posició. Les columnes s'emparellen per posició, no per nom. Un
DATEa la posició 2 d'una branca i unVARCHARa la 2 de l'altra dona error. - Esperar que l'àlies de la segona consulta es faci servir. Els noms del resultat els posa sempre la primera.
- Fer servir
UNIONquan voliesUNION ALL. Elimina files legítimament repetides i costa més. Per defecte,UNION ALL. - Fer servir
UNION ALLquan sí que hi havia solapament. A l'emulació delFULL OUTER JOIN(03-05) duplicaria totes les files coincidents. - Invertir l'ordre d'un
EXCEPT. No és commutatiu:proveidors EXCEPT clientsdona Alemanya; a l'inrevés, zero files. - Posar
ORDER BYen una consulta intermèdia.ERROR: syntax error at or near "UNION". Va al final, una sola vegada, i afecta el total. - Encadenar tres operadors sense parèntesis.
INTERSECTs'avalua abans queUNIONiEXCEPTa l'estàndard, però no a SQLite. La mateixa consulta retornava 11 files i 0 files segons l'agrupació. - Oblidar la columna d'origen en un
UNIONde fonts diferents. Sense ella no saps si "Laia Puig Sanchis" és una empleada o una clienta. - Consell: escriu la primera branca, executa-la, i només llavors afegeix les altres. Depurar un
UNIONde quatre consultes escrit d'una tirada és incòmode; els errors de tipus assenyalen la unió, no la columna culpable. - Consell: alinea visualment les branques. Mateix ordre de columnes, mateixa indentació i l'operador en una línia pròpia. Un
UNIONben formatat es revisa d'un cop d'ull. - Consell: fes servir
ORDER BY 1, 2a les consultes de conjunts. Els noms poden ser enganyosos quan les branques vénen de taules diferents; els ordinals, no.
Exercicis
Exercici 1
Construeix un directori de contactes espanyols: totes les persones i entitats de BotigaVerda el país de les quals sigui Espanya, amb una columna que indiqui el seu tipus (client, empleat o proveidor), el seu nom i la seva ciutat si n'hi ha.
Tingues en compte que empleats no té columna pais —tots treballen a Espanya— i que proveidors no té ciutat. Ordena per tipus i nom.
Exercici 2
Respon amb operadors de conjunts:
- A quines ciutats hi ha clients però cap empleat?
- A quines ciutats hi ha empleats però cap client?
- Explica per què els dos resultats són tan diferents en mida.
Exercici 3
Un company vol la llista de productes que s'han venut però no tenen cap ressenya, i escriu això:
SELECT producte_id FROM linies_comanda
EXCEPT
SELECT producte_id FROM ressenyes
UNION
SELECT id FROM productes
ORDER BY 1;- Com agrupa PostgreSQL aquesta consulta i què retorna realment?
- Escriu la consulta correcta per al que ell volia.
- Escriu la mateixa resposta fent servir un
JOINi un anti-join en lloc d'operadors de conjunts, retornant també el nom del producte. Quina de les dues prefereixes i per què?
Solucions
Solució 1
SELECT 'client' AS tipus,
c.nom || ' ' || c.cognoms AS nom,
c.ciutat
FROM clients AS c
WHERE c.pais = 'Espanya'
UNION ALL
SELECT 'empleat',
e.nom || ' ' || e.cognoms,
e.ciutat
FROM empleats AS e
UNION ALL
SELECT 'proveidor',
pr.nom,
NULL::varchar
FROM proveidors AS pr
WHERE pr.pais = 'Espanya'
ORDER BY tipus, nom;| tipus | nom | ciutat |
|---|---|---|
| client | Ana Belmonte Roca | Barcelona |
| client | Carlos Ferrer Ibáñez | València |
| client | Diego Ramos Herrera | Sevilla |
| client | Elena Navarro Puig | Alacant |
| client | Hugo Iglesias Pardo | Saragossa |
| client | Inés Carrasco Vega | València |
| client | Javier Ortega Ruiz | Madrid |
| client | Lucía Martínez Soler | València |
| client | Marta Sanchis Gil | Castelló |
| client | Núria Bosch Ferrer | Barcelona |
| client | Pau Llorens Vidal | València |
| empleat | Andrés Company Talens | València |
| empleat | Beatriz Nadal Ripoll | València |
| empleat | Daniel Vercher Lluch | València |
| empleat | Irene Salvador Mira | València |
| empleat | Laia Puig Sanchis | Castelló |
| empleat | Marc Estévez Roig | València |
| empleat | Óscar Peris Blasco | València |
| empleat | Rosa Alcázar Vives | València |
| proveidor | BioSierra Ibérica | (null) |
| proveidor | Huerta del Turia | (null) |
21 files = 11 clients espanyols + 8 empleats + 2 proveïdors espanyols.
Tres detalls del raonament:
empleatsno portaWHEREperquè la taula no té columnapais: per disseny, tot l'equip treballa a Espanya. És una suposició del model, i convé deixar-la escrita en un comentari perquè ningú no la doni per oblidada.- Els filtres van al
WHEREde cada branca, no al final. UnWHEREdesprés de l'últimUNION ALLpertanyeria només a la tercera consulta, no al conjunt. NULL::varcharmanté el nombre de columnes a la branca de proveïdors.
Solució 2
1. Ciutats amb clients però sense empleats:
| ciutat |
|---|
| Alacant |
| Barcelona |
| Lió |
| Lisboa |
| Madrid |
| París |
| Porto |
| Saragossa |
| Sevilla |
9 ciutats.
2. Ciutats amb empleats però sense clients:
Cap.
3. Per què són tan diferents. Perquè els dos conjunts tenen mides i naturaleses molt diferents:
| Conjunt | Ciutats diferents | Quines |
|---|---|---|
| Ciutats de clients | 11 | València, Castelló, Madrid, Barcelona, Alacant, Sevilla, Saragossa, Lisboa, Porto, Lió, París |
| Ciutats d'empleats | 2 | València, Castelló |
Les ciutats dels empleats són un subconjunt de les dels clients: BotigaVerda només té oficines a València (seu) i Castelló (on treballa la Laia), i a totes dues hi ha clients. Per això empleats EXCEPT clients és buit, mentre que l'operació inversa retorna les altres nou ciutats on hi ha clients i cap presència física.
Comprovació creuada amb la secció 5: 11 ciutats en total (UNION), 2 en comú (INTERSECT), 9 només de clients (EXCEPT) i 0 només d'empleats. Els números quadren: 2 + 9 + 0 = 11.
Solució 3
1. Com agrupa PostgreSQL. No hi ha INTERSECT a la consulta, així que els dos operadors restants s'avaluen d'esquerra a dreta:
La primera part dona els 8 productes venuts sense ressenya; la segona afegeix els 20 productes del catàleg. La unió de tots dos conjunts són... els 20 productes. La consulta retorna el catàleg sencer, que no té absolutament res a veure amb la pregunta. L'UNION amb productes anul·la tota la feina de l'EXCEPT.
2. La consulta correcta. La tercera branca sobra completament:
-- ✅ CORRECTA
SELECT producte_id FROM linies_comanda
EXCEPT
SELECT producte_id FROM ressenyes
ORDER BY 1;| producte_id |
|---|
| 3 |
| 4 |
| 7 |
| 8 |
| 9 |
| 11 |
| 14 |
| 17 |
8 productes venuts alguna vegada i mai ressenyats. Compara-ho amb els 11 productes sense ressenya de la secció 9: la diferència són els tres que mai no s'han venut (13, 19 i 20), que aquí no apareixen perquè no són a linies_comanda.
3. Amb JOIN i anti-join:
SELECT DISTINCT p.id,
p.nom AS producte,
p.preu
FROM productes AS p
INNER JOIN linies_comanda AS lc ON lc.producte_id = p.id
LEFT JOIN ressenyes AS r ON r.producte_id = p.id
WHERE r.id IS NULL
ORDER BY p.id;| id | producte | preu |
|---|---|---|
| 3 | Mel de tarongina crua 500 g | 9.75 |
| 4 | Pasta d'espelta 500 g | 2.80 |
| 7 | Xampú sòlid de romaní 80 g | 8.40 |
| 8 | Oli corporal d'ametlles 200 ml | 14.25 |
| 9 | Bàlsam labial de calèndula 15 ml | 4.60 |
| 11 | Fregall vegetal de lufa (pack 3) | 5.50 |
| 14 | Infusió de camamilla ecològica 20 u | 3.25 |
| 17 | Suc de taronja premsat en fred 1 L | 5.40 |
Els mateixos 8 productes, ara amb el seu nom i el seu preu. L'INNER JOIN amb linies_comanda exigeix que s'hagin venut; el LEFT JOIN amb ressenyes més el WHERE r.id IS NULL exigeix que no tinguin ressenya.
Quina preferir:
EXCEPT |
JOIN + anti-join |
|
|---|---|---|
| A favor | Curt, es llegeix com la pregunta, impossible equivocar-se amb els duplicats | Retorna la fila sencera: nom, preu, stock, el que calgui |
| En contra | Només retorna identificadors; per a l'informe cal tornar a productes |
Necessita un DISTINCT —perquè l'INNER JOIN multiplica per cada línia venuda— i cal raonar l'IS NULL |
Per a aquest cas concret, el JOIN és la millor opció, perquè un informe amb vuit números sense nom no serveix a ningú. L'EXCEPT seria preferible si la consulta fos un pas intermedi d'una comprovació automàtica, on només importen els identificadors.
I fixa't en el DISTINCT: és exactament el símptoma del qual parlàvem a 02-04 i 03-02. Apareix perquè l'INNER JOIN amb linies_comanda genera una fila per venda, i la pregunta és sobre productes, no sobre vendes. Al mòdul 7 veuràs que WHERE EXISTS (...) resol això sense DISTINCT i sense multiplicar files.
Conclusió
Tanques l'última peça del mòdul:
- Els operadors de conjunts combinen en vertical: apilen files de consultes independents, mentre que els
JOINcombinen en horitzontal afegint columnes. No necessiten cap relació entre les taules. - Les regles de compatibilitat són tres: mateix nombre de columnes, tipus compatibles per posició i noms del resultat presos de la primera consulta. Els buits s'omplen amb literals com
NULL::varchar. UNION ALLés l'opció per defecte: no dedupla, és més ràpid i no esborra files legítimament repetides.UNIONnomés quan eliminar duplicats sigui l'objectiu, com a la llista d'11 ciutats enfront de les 23 d'UNION ALL.- Has construït la llista unificada de contactes (28 files de tres taules sense relació entre si) amb una columna d'origen literal, que és el que fa llegible qualsevol
UNIONde fonts heterogènies. INTERSECTretorna el que és als dos costats i és commutatiu: dues ciutats amb clients i empleats, tres països amb clients i proveïdors.EXCEPTretorna el del primer que no és al segon i no és commutatiu: Alemanya en un sentit, zero files en l'altre. A Oracle es diuMINUS.- Saps quan triar
EXCEPTi quan l'anti-join de 03-03:EXCEPTper a llistes d'identificadors, anti-join quan necessites les dades de les files. INTERSECTté precedència sobreUNIONiEXCEPT—llevat de SQLite, que avalua d'esquerra a dreta—, i la mateixa consulta pot retornar 11 files o 0 segons com s'agrupi. Fes servir parèntesis sempre que encadenis tres o més.ORDER BYiLIMITvan al final i s'apliquen al resultat combinat, fent servir els noms de columna de la primera consulta o els ordinals.
I amb això tanques el mòdul 3
Les nou taules de BotigaVerda han deixat de ser nou illes. Saps:
- Que un
JOINés un producte cartesià filtrat, que la seva condició va a l'ONi que elsJOINes resolen dins delFROM, abans delWHERE. - Fer servir l'
INNER JOINi predir quines files perd, amb la consulta canònica de quatre taules —linies_comanda+comandes+clients+productes— que t'acompanyarà fins al projecte final. - Fer servir el
LEFT JOINper conservar el que no casa, el patró anti-join per trobar-ho, i per què una condició alWHEREsobre la taula dreta degrada silenciosament elLEFTaINNER. - Que el
RIGHT JOINés el seu mirall i sempre es pot reescriure com aLEFT, i que barrejar-los en una cadena fa la consulta il·legible. - Que el
FULL OUTER JOINserveix per conciliar dues fonts independents, i que la integritat referencial el fa innecessari dins d'un mateix esquema. - Recórrer les relacions reflexives amb
SELF JOIN—l'organigrama i la xarxa de referits— i generar combinacions completes ambCROSS JOIN. - I apilar resultats sencers amb
UNION,INTERSECTiEXCEPT.
Però fixa't en què retornen totes les consultes que has escrit en aquest mòdul: files de detall. Una fila per línia de comanda, una per client, una per parella de productes. I les preguntes que la direcció de BotigaVerda fa de debò no demanen detall, demanen totals: quant factura cada categoria, quantes comandes ha fet cada client, quina és la puntuació mitjana de cada producte, quin comercial tanca més vendes, quants clients hi ha per país. Per respondre-les cal resumir moltes files en una de sola, i això és una operació que encara no saps fer.
Al mòdul 4, Filtratge avançat i agregació, arribaran les dues meitats que falten. Primer afinaràs el filtratge: LIKE per buscar per patrons de text, IN i BETWEEN per a rangs i llistes, i el tractament seriós dels NULL amb IS NULL, IS NOT NULL i la seva lògica de tres valors —aquella que ja has vist treure el nas a l'ON, al WHERE i a cada LEFT JOIN d'aquest mòdul—. I després arribarà l'agregació: les funcions COUNT, SUM, AVG, MIN i MAX, la clàusula GROUP BY que parteix el resultat en grups i HAVING per filtrar aquests grups. Allà tornarà, aquesta vegada amb conseqüències reals, l'advertiment que has llegit tres vegades en aquest mòdul: compte amb sumar valors de capçalera després d'unir amb el detall.
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
