COALESCE sap respondre una sola pregunta: "és nul?". Totes les altres —és car?, queda poc estoc?, en quin tram cau aquest client?— necessiten alguna cosa més general. CASE és aquesta cosa: l'if de SQL, i l'última peça del mòdul 6. Amb ell una consulta deixa de limitar-se a retornar i transformar dades i comença a decidir: classificar un catàleg en gammes de preu, posar un semàfor d'estoc, ordenar els estats d'una comanda pel seu ordre de flux en lloc d'alfabèticament, o convertir files en columnes per a un informe de direcció.
Contingut
CASEés una expressió, no una sentènciaCASEsimple enfront deCASEcercatELSE, i què passa quan hi falta- L'ordre d'avaluació: la primera condició certa guanya
CASEalSELECT: classificarCASEalWHEREi a l'ORDER BYCASEalGROUP BY- El patró de taula pivot
CASE,FILTERiCOALESCE: quan cadascun- Errors habituals i consells
- Exercicis
- Conclusió del mòdul
CASE és una expressió, no una sentència
CASE és una expressió, no una sentènciaLa idea que cal fixar abans que la sintaxi: CASE retorna un valor. No executa blocs de codi, no salta, no controla el flux del programa. És una expressió, exactament igual que preu * 1.21 o UPPER(nom).
D'aquí es deriva tota la resta: pot anar a qualsevol lloc on càpiga un valor (SELECT, WHERE, ORDER BY, GROUP BY, HAVING, dins d'una funció o d'un agregat); totes les seves branques han de retornar un tipus compatible, no un número en una i un text en una altra; i retorna exactament un valor per fila, com tota funció escalar (06-01).
SELECT id, nom, preu,
CASE WHEN preu >= 15 THEN 'Premium' ELSE 'Estandard' END AS gamma,
preu * CASE WHEN preu >= 15 THEN 0.90 ELSE 1.00 END AS preu_promocio
FROM productes WHERE id IN (5, 6, 15) ORDER BY id;| id | nom | preu | gamma | preu_promocio |
|---|---|---|---|---|
| 5 | Tomàquet triturat ecològic 400 g | 1.95 | Estandard | 1.9500 |
| 6 | Crema facial d'àloe vera 50 ml | 18.90 | Premium | 17.0100 |
| 15 | Te verd matcha cerimonial 30 g | 22.00 | Premium | 19.8000 |
El segon CASE és dins d'una multiplicació: és un operand més. I fixa't en els quatre decimals de preu_promocio: NUMERIC(10,2) * NUMERIC(3,2) dona escala 4, i arrodoniries en presentar (06-02).
No ho confonguis amb l'
IFprocedimental. PL/pgSQL —el llenguatge dels procediments, mòdul 10— sí que té una sentènciaIF … THEN … END IFque executa codi; elCASEde SQL no executa res, s'avalua i produeix un valor.
CASE simple enfront de CASE cercat
CASE simple enfront de CASE cercatHi ha dues formes i no són intercanviables. El CASE simple compara una expressió amb una llista de valors; el cercat, condicions completes i independents:
CASE expressio CASE WHEN condicio1 THEN resultat1
WHEN valor1 THEN resultat1 WHEN condicio2 THEN resultat2
WHEN valor2 THEN resultat2 ELSE resultat_per_omissio
ELSE resultat_per_omissio END
ENDCASE simple |
CASE cercat |
|
|---|---|---|
| Compara | Una expressió amb valors concrets | Condicions booleanes qualssevol |
| Operadors | Només igualtat implícita | >, <, BETWEEN, LIKE, IS NULL, AND, OR… |
Diverses columnes / detecta NULL |
No / No (vegeu més avall) | Sí / Sí, amb IS NULL |
| Quan fer-lo servir | Traduir codis, dominis tancats | Rangs, comparacions, tota la resta |
El CASE simple és perfecte per traduir un domini tancat —metode_pagament, estat, un codi de país— i el cercat serveix per a tota la resta. I hi ha un cas on el simple no pot fer la feina:
-- ⚠️ INCORRECTA: mai no entra a la branca del NULL
SELECT id, CASE empleat_id WHEN NULL THEN 'Venda web' ELSE 'Amb comercial' END AS canal
FROM comandes WHERE id IN (1, 2) ORDER BY id;
-- ✅ CORRECTA
SELECT id, CASE WHEN empleat_id IS NULL THEN 'Venda web' ELSE 'Amb comercial' END AS canal
FROM comandes WHERE id IN (1, 2) ORDER BY id;| id | canal (incorrecta) | canal (correcta) |
|---|---|---|
| 1 | Amb comercial | Venda web |
| 2 | Amb comercial | Amb comercial |
La comanda 1 no té comercial i la primera consulta diu "Amb comercial". La raó és 04-03 al peu de la lletra: el CASE simple compara amb = i empleat_id = NULL s'avalua a UNKNOWN, mai a TRUE. La branca WHEN NULL és codi mort: no s'executarà mai, en cap fila.
Regla: tan bon punt un
NULLentra en joc,CASEcercat ambIS NULL. El simple no pot detectar l'absència de valor i, el pitjor, no dona error: retorna la branca equivocada en silenci.
ELSE, i què passa quan hi falta
ELSE, i què passa quan hi faltaELSE és opcional, i si l'omets i cap condició no es compleix, CASE retorna NULL.
SELECT id, estat,
CASE estat WHEN 'lliurat' THEN 'Tancat' WHEN 'cancellat' THEN 'Anullat' END
AS situacio_sense_else
FROM comandes WHERE id IN (1, 6, 17, 20) ORDER BY id;| id | estat | situacio_sense_else |
|---|---|---|
| 1 | lliurat | Tancat |
| 6 | cancellat | Anullat |
| 17 | enviat | (null) |
| 20 | pendent | (null) |
Els estats enviat i pendent no encaixen en cap branca i surten NULL. Aquests nuls són traïdors perquè no vénen de les dades: els fabrica la teva pròpia expressió. Si després agrupes per aquesta columna tindràs un grup NULL que ningú no ha demanat; si la fas servir en un WHERE, aquelles files desapareixeran (04-03). Escriu sempre l'ELSE, encara que sigui ELSE 'Altre' o ELSE NULL explícit: un ELSE NULL a mà diu "he pensat en aquest cas" i un ELSE absent diu "me'n vaig oblidar", i d'aquí a sis mesos no sabràs quin era.
- L'ordre d'avaluació: la primera condició certa guanya
CASE avalua els seus WHEN de dalt a baix i s'atura al primer que sigui TRUE; els altres ni tan sols s'avaluen (és la mateixa peresa de COALESCE, que no en va és un CASE disfressat). Això converteix l'ordre en part de la lògica i produeix l'error més freqüent amb CASE: posar el rang més ampli primer.
-- ⚠️ INCORRECTA: tot cau a la primera branca
SELECT id, nom, preu,
CASE WHEN preu < 25 THEN 'Economic' WHEN preu < 15 THEN 'Mitja'
WHEN preu < 5 THEN 'Barat' ELSE 'Premium' END AS gamma
FROM productes WHERE id IN (5, 8, 15) ORDER BY id;| id | nom | preu | gamma |
|---|---|---|---|
| 5 | Tomàquet triturat ecològic 400 g | 1.95 | Economic |
| 8 | Oli corporal d'ametlles 200 ml | 14.25 | Economic |
| 15 | Te verd matcha cerimonial 30 g | 22.00 | Economic |
Els tres productes, del més barat al més car, cauen a la mateixa gamma: tots compleixen preu < 25 i les altres condicions no s'avaluen mai. La consulta no dona error; simplement classifica malament el catàleg sencer. La versió correcta ordena les condicions de la més restrictiva a la més general:
-- ✅ CORRECTA
SELECT id, nom, preu,
CASE WHEN preu < 5 THEN 'Economic' WHEN preu < 15 THEN 'Mitja'
ELSE 'Premium' END AS gamma
FROM productes WHERE id IN (5, 8, 15) ORDER BY id;| id | nom | preu | gamma |
|---|---|---|---|
| 5 | Tomàquet triturat ecològic 400 g | 1.95 | Economic |
| 8 | Oli corporal d'ametlles 200 ml | 14.25 | Mitja |
| 15 | Te verd matcha cerimonial 30 g | 22.00 | Premium |
Com que la primera certa guanya, cada branca només necessita el seu límit superior: no cal escriure WHEN preu >= 5 AND preu < 15. Aquesta és l'elegància del CASE en cascada, i també la seva trampa: si reordenes les línies, canvies el resultat.
flowchart LR
B{"preu < 5?"} -->|"sí"| C["'Economic'"]
B -->|"no"| D{"preu < 15?"} -->|"sí"| E["'Mitja'"]
D -->|"no"| F["ELSE → 'Premium'"]
CASE al SELECT: classificar
CASE al SELECT: classificarL'ús principal. Un semàfor d'estoc combinat amb la gamma de preu:
SELECT id, nom, preu, stock,
CASE WHEN preu < 5 THEN 'Economic'
WHEN preu < 15 THEN 'Mitja'
ELSE 'Premium' END AS gamma,
CASE WHEN stock = 0 THEN '🔴 Sense stock'
WHEN stock < 50 THEN '🟠 Baix'
WHEN stock < 150 THEN '🟡 Normal'
ELSE '🟢 Alt' END AS semafor
FROM productes WHERE id IN (5, 8, 13, 15) ORDER BY id;| id | nom | preu | stock | gamma | semafor |
|---|---|---|---|---|---|
| 5 | Tomàquet triturat ecològic 400 g | 1.95 | 300 | Economic | 🟢 Alt |
| 8 | Oli corporal d'ametlles 200 ml | 14.25 | 45 | Mitja | 🟠 Baix |
| 13 | Espelmes de cera de soja (pack 2) | 13.75 | 0 | Mitja | 🔴 Sense stock |
| 15 | Te verd matcha cerimonial 30 g | 22.00 | 40 | Premium | 🟠 Baix |
El producte 13 —l'únic del catàleg amb estoc 0— queda identificat sense buscar-lo. Aquest és el valor d'un semàfor: convertir un número que cal interpretar en una etiqueta que es llegeix d'un cop d'ull.
CASE al WHERE i a l'ORDER BY
CASE al WHERE i a l'ORDER BYAl WHERE: es pot, però gairebé mai no convé
Com que CASE retorna un valor, el pots comparar:
-- ⚠️ Funciona, però és rebuscat
SELECT id, nom FROM productes
WHERE CASE WHEN preu < 5 THEN 'Economic' ELSE 'Altre' END = 'Economic';
-- ✅ CORRECTA: diu el mateix en una línia
SELECT id, nom FROM productes WHERE preu < 5;Totes dues retornen els 7 productes de menys de 5 €, però un CASE al WHERE és més llarg, més difícil de llegir i —el que importa— impedeix fer servir l'índex, perquè la columna queda embolcallada en una expressió (08-03). Gairebé sempre el que vols és un OR, un IN o un BETWEEN. L'única excepció raonable és un filtre la condició del qual depèn d'un paràmetre (WHERE columna = CASE WHEN $1 = 'tots' THEN columna ELSE $1 END), i fins i tot allà hi ha solucions millors.
A l'ORDER BY: aquí sí, i molt
Aquí CASE no té substitut. Els estats d'una comanda tenen un ordre de flux —pendent → pagat → enviat → lliurat, amb cancellat a part— que no coincideix amb l'alfabètic:
SELECT estat, COUNT(*) AS comandes
FROM comandes GROUP BY estat
ORDER BY CASE estat WHEN 'pendent' THEN 1 WHEN 'pagat' THEN 2
WHEN 'enviat' THEN 3 WHEN 'lliurat' THEN 4
WHEN 'cancellat' THEN 5 END;| estat | comandes |
|---|---|
| pendent | 1 |
| pagat | 2 |
| enviat | 2 |
| lliurat | 14 |
| cancellat | 1 |
Ordenat alfabèticament sortiria cancellat, enviat, lliurat, pagat, pendent: una seqüència sense significat que obliga el lector a recompondre mentalment el cicle de vida. Aquí el CASE és la informació. Fixa't en dos detalls: és un CASE simple, perquè compara amb valors d'un domini tancat, i l'expressió de l'ORDER BY no apareix al SELECT, cosa perfectament legal (02-05). Una altra variant molt útil, "això primer i la resta després", és ORDER BY CASE WHEN estat = 'pendent' THEN 0 ELSE 1 END, data_comanda DESC.
CASE al GROUP BY
CASE al GROUP BYSi pots classificar al SELECT, pots agrupar per la classificació. L'única regla és la de 04-05: repetir l'expressió completa al GROUP BY, no l'àlies.
SELECT CASE WHEN preu < 5 THEN 'Economic'
WHEN preu < 15 THEN 'Mitja'
ELSE 'Premium' END AS gamma,
COUNT(*) AS productes, ROUND(AVG(preu), 2) AS preu_mitja
FROM productes
GROUP BY CASE WHEN preu < 5 THEN 'Economic'
WHEN preu < 15 THEN 'Mitja'
ELSE 'Premium' END
ORDER BY preu_mitja;| gamma | productes | preu_mitja |
|---|---|---|
| Economic | 7 | 3.56 |
| Mitja | 10 | 9.85 |
| Premium | 3 | 19.10 |
7 + 10 + 3 = 20 productes, i la mitjana general del catàleg continua essent 9,035 €. La duplicació de l'expressió és lletja però necessària: el GROUP BY s'avalua abans que el SELECT (ordre lògic del mòdul 2) i l'àlies gamma encara no existeix.
Nota de dialecte: PostgreSQL i SQL Server exigeixen repetir l'expressió; MySQL, SQLite i MariaDB permeten
GROUP BY gammafent servir l'àlies delSELECT, que és còmode i no és estàndard. L'alternativa neta —donar nom a la classificació una sola vegada— és una CTE, i arriba a 10-02.
- El patró de taula pivot
I arribem a l'ús més potent de CASE: ficar-lo dins d'un agregat per convertir files en columnes. El problema de partida és l'informe de 04-05: la facturació per categoria i any surt com una llista llarga amb una fila per combinació, i direcció la vol com a taula de doble entrada. La idea és d'una simplicitat enganyosa: SUM(CASE WHEN any_ = 2025 THEN import ELSE 0 END) suma només el del 2025, perquè la resta de files hi aporten un zero; repeteix el truc amb una altra condició i tens una altra columna.
SELECT cat.id,
cat.nom AS categoria,
ROUND(SUM(CASE WHEN EXTRACT(YEAR FROM co.data_comanda) = 2025
THEN lc.quantitat * lc.preu_unitari * (1 - lc.descompte)
ELSE 0 END), 2) AS any_2025,
ROUND(SUM(CASE WHEN EXTRACT(YEAR FROM co.data_comanda) = 2026
THEN lc.quantitat * lc.preu_unitari * (1 - lc.descompte)
ELSE 0 END), 2) AS any_2026,
ROUND(SUM(CASE WHEN lc.id IS NOT NULL
THEN lc.quantitat * lc.preu_unitari * (1 - lc.descompte)
ELSE 0 END), 2) AS total
FROM categories AS cat
LEFT JOIN productes AS p ON p.categoria_id = cat.id
LEFT JOIN linies_comanda AS lc ON lc.producte_id = p.id
LEFT JOIN comandes AS co ON co.id = lc.comanda_id
GROUP BY cat.id, cat.nom ORDER BY cat.id;| id | categoria | any_2025 | any_2026 | total |
|---|---|---|---|---|
| 1 | Alimentació | 215.67 | 40.60 | 256.27 |
| 2 | Cosmètica natural | 128.22 | 28.10 | 156.32 |
| 3 | Llar sostenible | 88.58 | 0.00 | 88.58 |
| 4 | Begudes | 146.55 | 48.73 | 195.28 |
| 5 | Higiene personal | 24.50 | 7.00 | 31.50 |
| 6 | Complements | 0.00 | 0.00 | 0.00 |
Les columnes quadren amb les xifres canòniques del curs: 603,52 € el 2025, 124,43 € el 2026 i 727,95 € en total. I la lectura és immediata: Llar sostenible no ha venut res el 2026 i Complements no ha venut mai — una cosa que en una llista de dotze files per categoria i any es veu molt pitjor.
Tres coses que cal entendre del patró. Les columnes són fixes: si demà hi ha dades del 2027 cal editar la consulta, perquè SQL decideix la llista de columnes en analitzar-la, abans de llegir ni una sola dada. L'ELSE 0 és el motor del truc: sense ell les files que no compleixen hi aportarien NULL, i un grup sencer de nuls donaria NULL en lloc de 0. I serveix igual amb COUNT, AVG o MAX — amb COUNT s'escriu sense ELSE, precisament perquè COUNT ignora els nuls.
Un altre pivot, aquesta vegada amb COUNT: mètodes de pagament per país del client.
SELECT co.metode_pagament,
COUNT(CASE WHEN c.pais = 'Espanya' THEN 1 END) AS espanya,
COUNT(CASE WHEN c.pais = 'Portugal' THEN 1 END) AS portugal,
COUNT(CASE WHEN c.pais = 'França' THEN 1 END) AS franca,
COUNT(*) AS total
FROM comandes AS co JOIN clients AS c ON c.id = co.client_id
GROUP BY co.metode_pagament ORDER BY total DESC, co.metode_pagament;| metode_pagament | espanya | portugal | franca | total |
|---|---|---|---|---|
| targeta | 9 | 1 | 1 | 11 |
| paypal | 2 | 2 | 0 | 4 |
| transferencia | 2 | 0 | 1 | 3 |
| contrareemborsament | 1 | 0 | 1 | 2 |
11 + 4 + 3 + 2 = 20 comandes. La targeta domina a Espanya, mentre que els dos clients portuguesos amb comandes fan servir exclusivament PayPal: una conclusió comercial que la llista plana no deixava veure.
Alternativa:
crosstab. L'extensiótablefuncde PostgreSQL portacrosstab(), que genera pivots a partir d'una consulta de tres columnes (fila, columna, valor). Estalvia escriure unCASEper columna, però obliga a declarar els tipus de sortida a mà i és específica de PostgreSQL; la veuràs aplicada a la pràctica del mòdul 11. Per a tres o quatre columnes,SUM(CASE …)continua essent l'opció més clara i l'única portable.
CASE, FILTER i COALESCE: quan cadascun
CASE, FILTER i COALESCE: quan cadascunA 04-04 vas conèixer FILTER (WHERE …) i es va dir que el seu equivalent amb CASE s'explicaria aquí. Aquestes dues expressions diuen el mateix:
FILTER (WHERE …) |
SUM(CASE WHEN …) |
|
|---|---|---|
| Llegibilitat | Molt alta: la condició està separada | Mitjana: la condició va a dins |
| Disponibilitat | PostgreSQL 9.4+; no existeix a MySQL, SQL Server ni SQLite | Tots els motors |
| Grup sense cap fila que hi passi | Retorna NULL |
Retorna 0 si hi poses ELSE 0 |
| Fora d'un agregat | No es pot | Sí |
La penúltima fila és la que decideix moltes vegades: al pivot anterior, Complements mostra 0.00 perquè l'ELSE 0 hi aporta zeros; amb FILTER mostraria *(null)* i caldria embolcallar-ho en un COALESCE (06-04). Cap de les dues no és millor: FILTER és més llegible, CASE … ELSE 0 és més portable i controla el valor per omissió.
I la regla que tanca el quartet:
| Situació | Eina |
|---|---|
| "Si és nul, posa-hi això altre" | COALESCE |
| "Si és nul o compleix una altra condició…" | CASE |
| "Converteix aquest valor concret en nul" | NULLIF |
| "Agrega només les files que compleixen X" | FILTER o CASE dins de l'agregat |
COALESCE(cost, 0) i CASE WHEN cost IS NULL THEN 0 ELSE cost END són idèntics i el primer és millor; però tan bon punt la regla es complica —"si el cost és nul posa-hi 0, i si a més el producte està descatalogat posa-hi −1"— COALESCE no hi arriba i CASE sí.
Errors habituals i consells
- Posar el rang més ampli primer. La primera condició certa guanya i les altres són codi mort: de la més restrictiva a la més general. I el
CASEsimple ambNULL(CASE columna WHEN NULL THEN …) mai no entra en aquella branca: fes servirCASE WHEN columna IS NULL THEN …. - Ometre l'
ELSE. Les files que no encaixen surtenNULL, i són nuls que fabrica la teva consulta, no les dades. - Barrejar tipus entre branques. Totes han de retornar un tipus compatible; si no,
ERROR: CASE types text and integer cannot be matched. - Fer servir l'àlies del
CASEalGROUP BY. A PostgreSQL cal repetir l'expressió, perquè elGROUP BYs'avalua abans que elSELECT. I evita ficar unCASEalWHEREquan n'hi ha prou amb unOR: més llarg, menys llegible i sense índex (08-03). - Oblidar l'
ELSE 0en un pivot ambSUM. El grup sense files coincidents donaràNULLen lloc de0. I a l'inrevés: ambCOUNTl'ELSE 0hi sobra i falseja el recompte, perquè0no és nul i es compta. - Esperar que un pivot generi columnes tot sol. La llista de columnes és fixa; un any nou exigeix editar la consulta.
- Consell: si el
CASEté més de quatre branques, planteja't una taula de referència. UnCASEde vint línies repetit en deu informes és una taula de domini que algú hauria d'haver creat (mòdul 5). - Consell: fes servir
FILTERa PostgreSQL iCASEquan necessitis portabilitat, i alinea les branques verticalment: unCASEben indentat es llegeix com una taula.
Exercicis
Exercici 1
Direcció vol el quadre de comandament del catàleg. Agrupa els productes actius per gamma (< 5 Economic, < 15 Mitja, la resta Premium) i mostra el nombre de productes, el preu mitjà i quants d'ells tenen menys de 50 unitats en estoc. (Pista: COUNT(CASE WHEN … THEN 1 END) o FILTER.)
Exercici 2
Un company ha escrit aquesta classificació de ports i conclou que "totes les nostres comandes porten ports":
-- ⚠️ Sospitosa
SELECT id, estat,
CASE WHEN despeses_enviament >= 0 THEN 'Amb ports'
WHEN despeses_enviament > 10 THEN 'Ports cars'
WHEN despeses_enviament = 0 THEN 'Enviament gratis'
END AS tipus_enviament
FROM comandes ORDER BY id;(1) Quantes etiquetes diferents pot retornar realment i per què? (2) Corregeix-la perquè distingeixi de debò els tres casos i dona el recompte de cadascun. (3) La consulta no té ELSE: per què no es nota, i quan es notaria?
Exercici 3
Construeix la taula pivot de comandes per estat i any: una fila per estat i columnes c2025, c2026 i total, ordenada per l'ordre de flux (pendent, pagat, enviat, lliurat, cancel·lat) i no alfabèticament. Escriu-la dues vegades, amb SUM(CASE …) i amb COUNT(*) FILTER (WHERE …), i explica en què es diferencien els resultats.
Solucions
Solució 1
SELECT CASE WHEN preu < 5 THEN 'Economic'
WHEN preu < 15 THEN 'Mitja'
ELSE 'Premium' END AS gamma,
COUNT(*) AS productes,
ROUND(AVG(preu), 2) AS preu_mitja,
COUNT(CASE WHEN stock < 50 THEN 1 END) AS amb_stock_critic
FROM productes WHERE actiu = TRUE
GROUP BY CASE WHEN preu < 5 THEN 'Economic'
WHEN preu < 15 THEN 'Mitja'
ELSE 'Premium' END
ORDER BY preu_mitja;| gamma | productes | preu_mitja | amb_stock_critic |
|---|---|---|---|
| Economic | 7 | 3.56 | 0 |
| Mitja | 10 | 9.85 | 2 |
| Premium | 2 | 20.45 | 1 |
19 productes, no 20: el WHERE actiu = TRUE exclou el 20 (Càpsules d'espirulina, 16,40 €), que era Premium — per això aquella gamma baixa de 3 a 2 i el seu preu mitjà puja a 20,45 €. Els tres productes amb estoc crític són el 8 (45 unitats), el 13 (0) i el 15 (40), repartits entre Mitja i Premium: els productes cars són els que es queden sense existències. I COUNT(CASE WHEN stock < 50 THEN 1 END) va sense ELSE expressament: les files que no compleixen retornen NULL i COUNT les ignora; amb ELSE 0 en comptaria els 19.
Solució 2
1. Només una: 'Amb ports'. Totes les despeses_enviament de BotigaVerda són >= 0 —la restricció CHECK de 01-06 ho garanteix—, així que la primera condició és certa per a les vint files i les altres dues són codi mort. És l'error de la secció 4 en estat pur: la condició més general, primer. 2. Ordenant del més restrictiu al més general:
-- ✅ CORRECTA
SELECT CASE WHEN despeses_enviament = 0 THEN 'Enviament gratis'
WHEN despeses_enviament > 10 THEN 'Ports cars'
ELSE 'Ports normals' END AS tipus_enviament,
COUNT(*) AS comandes,
ROUND(SUM(despeses_enviament), 2) AS ports
FROM comandes
GROUP BY CASE WHEN despeses_enviament = 0 THEN 'Enviament gratis'
WHEN despeses_enviament > 10 THEN 'Ports cars'
ELSE 'Ports normals' END
ORDER BY ports DESC;| tipus_enviament | comandes | ports |
|---|---|---|
| Ports normals | 13 | 80.75 |
| Ports cars | 3 | 37.50 |
| Enviament gratis | 4 | 0.00 |
20 comandes i 118,25 € de ports, la xifra canònica del mòdul 4: quatre comandes amb enviament gratuït i tres amb ports de 12,50 €.
3. No es nota únicament perquè la primera condició captura totes les files. És una bomba de rellotgeria: el dia que algú insereixi una comanda amb despeses_enviament nul —avui impossible pel NOT NULL, però els esquemes canvien (05-06)—, aquella fila retornaria NULL a tipus_enviament i seria un grup fantasma a l'informe. Escriu sempre l'ELSE.
Solució 3
SELECT estat,
SUM(CASE WHEN EXTRACT(YEAR FROM data_comanda) = 2025 THEN 1 ELSE 0 END) AS c2025,
SUM(CASE WHEN EXTRACT(YEAR FROM data_comanda) = 2026 THEN 1 ELSE 0 END) AS c2026,
COUNT(*) AS total
FROM comandes GROUP BY estat
ORDER BY CASE estat WHEN 'pendent' THEN 1 WHEN 'pagat' THEN 2
WHEN 'enviat' THEN 3 WHEN 'lliurat' THEN 4
WHEN 'cancellat' THEN 5 END;| estat | c2025 | c2026 | total |
|---|---|---|---|
| pendent | 0 | 1 | 1 |
| pagat | 0 | 2 | 2 |
| enviat | 1 | 1 | 2 |
| lliurat | 14 | 0 | 14 |
| cancellat | 1 | 0 | 1 |
La versió amb FILTER canvia només les dues columnes centrals, que passen a ser COUNT(*) FILTER (WHERE EXTRACT(YEAR FROM data_comanda) = 2025) AS c2025 i el seu equivalent per al 2026. I aquí els resultats coincideixen exactament, inclosos els zeros, perquè COUNT sobre un conjunt buit retorna 0 i no NULL (04-04): FILTER amb COUNT és segur. Si en lloc de COUNT(*) fessis servir SUM(despeses_enviament) FILTER (…), els grups sense files d'aquell any donarien *(null)* mentre que SUM(CASE … ELSE 0 END) donaria 0.00. La diferència no és FILTER enfront de CASE: és què retorna cada agregat quan no té res per agregar.
L'informe, llegit: BotigaVerda té 14 comandes lliurades, totes del 2025, i les quatre del 2026 continuen en curs —una de pendent, dues de pagades, una d'enviada—.
Conclusió del mòdul
Amb CASE tanques el mòdul 6:
CASEés una expressió, no una sentència: retorna un valor i cap a qualsevol clàusula, fins i tot dins d'una multiplicació o d'un agregat.- Distingeixes el
CASEsimple —per a dominis tancats— del cercat —per a rangs i condicions—, i saps que el simple no pot detectarNULLperquè compara amb=. Escrius sempre l'ELSE, perquè sense ell les files que no encaixen retornen nuls que fabrica la teva pròpia consulta. - Ordenes les condicions de la més restrictiva a la més general: la primera certa guanya i la resta és codi mort.
- El fas servir al
SELECTper classificar, a l'ORDER BYper imposar un ordre de negoci que l'alfabètic no pot donar, i alGROUP BYper agregar per la classificació repetint l'expressió completa. - Domines el patró de taula pivot,
SUM(CASE WHEN … THEN … ELSE 0 END), amb els seus límits (columnes fixes) i el seu equivalent modernFILTER. I saps triar entreCOALESCE,NULLIF,CASEiFILTERsegons què estiguis preguntant.
I amb aquesta lliçó s'acaba el mòdul 6 sencer. Vas començar sense poder ajuntar nom i cognoms en una columna; ara composes text, extreus gramatges amb expressions regulars, arrodoneixes diners sense perdre cèntims, agrupes per mes i per trimestre, converteixes tipus amb intenció, domes els nuls i classifiques files amb lògica condicional. Les teves consultes ja no retornen dades: retornen respostes.
Però totes aquestes respostes es calculen mirant una fila cada vegada, o un grup cada vegada. I hi ha una família sencera de preguntes que no funciona així, perquè necessita comparar cada fila amb el resultat d'una altra consulta: quins productes estan per sobre de la mitjana de la seva categoria?; quins clients tenen un tiquet mitjà superior a la mitjana general —la pregunta que va quedar explícitament pendent a 04-06 quan vas descobrir que HAVING no pot referir-se a un agregat global—; quines comandes inclouen el producte més car del catàleg?; quins clients no han comprat mai, sense recórrer a un LEFT JOIN amb IS NULL? Totes comparteixen la mateixa forma: una consulta dins d'una altra consulta. Al mòdul 7, Subconsultes, aprendràs a escriure-les: subconsultes escalars i de llista, correlacionades —que s'executen una vegada per fila—, EXISTS i NOT EXISTS, subconsultes al SELECT, al FROM i al WHERE, i el criteri per decidir quan una subconsulta és l'eina adequada i quan el correcte és un 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
