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

  1. CASE és una expressió, no una sentència
  2. CASE simple enfront de CASE cercat
  3. ELSE, i què passa quan hi falta
  4. L'ordre d'avaluació: la primera condició certa guanya
  5. CASE al SELECT: classificar
  6. CASE al WHERE i a l'ORDER BY
  7. CASE al GROUP BY
  8. El patró de taula pivot
  9. CASE, FILTER i COALESCE: quan cadascun
  10. Errors habituals i consells
  11. Exercicis
  12. Conclusió del mòdul

  1. CASE és una expressió, no una sentència

La 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'IF procedimental. PL/pgSQL —el llenguatge dels procediments, mòdul 10— sí que té una sentència IF … THEN … END IF que executa codi; el CASE de SQL no executa res, s'avalua i produeix un valor.

  1. CASE simple enfront de CASE cercat

Hi 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
END
CASE 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) / , 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 NULL entra en joc, CASE cercat amb IS NULL. El simple no pot detectar l'absència de valor i, el pitjor, no dona error: retorna la branca equivocada en silenci.

  1. ELSE, i què passa quan hi falta

ELSE é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.

  1. 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'"]

  1. CASE al SELECT: classificar

L'ú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.

  1. CASE al WHERE i a l'ORDER BY

Al 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 fluxpendentpagatenviatlliurat, 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.

  1. CASE al GROUP BY

Si 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 gamma fent servir l'àlies del SELECT, 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.

  1. 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ó tablefunc de PostgreSQL porta crosstab(), que genera pivots a partir d'una consulta de tres columnes (fila, columna, valor). Estalvia escriure un CASE per 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.

  1. CASE, FILTER i COALESCE: quan cadascun

A 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:

SUM(import) FILTER (WHERE any_ = 2025)   -- ≡   SUM(CASE WHEN any_ = 2025 THEN import END)
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

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 CASE simple amb NULL (CASE columna WHEN NULL THEN …) mai no entra en aquella branca: fes servir CASE WHEN columna IS NULL THEN ….
  • Ometre l'ELSE. Les files que no encaixen surten NULL, 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 CASE al GROUP BY. A PostgreSQL cal repetir l'expressió, perquè el GROUP BY s'avalua abans que el SELECT. I evita ficar un CASE al WHERE quan n'hi ha prou amb un OR: més llarg, menys llegible i sense índex (08-03).
  • Oblidar l'ELSE 0 en un pivot amb SUM. El grup sense files coincidents donarà NULL en lloc de 0. I a l'inrevés: amb COUNT l'ELSE 0 hi sobra i falseja el recompte, perquè 0 no é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 CASE té més de quatre branques, planteja't una taula de referència. Un CASE de vint línies repetit en deu informes és una taula de domini que algú hauria d'haver creat (mòdul 5).
  • Consell: fes servir FILTER a PostgreSQL i CASE quan necessitis portabilitat, i alinea les branques verticalment: un CASE ben 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 CASE simple —per a dominis tancats— del cercat —per a rangs i condicions—, i saps que el simple no pot detectar NULL perquè 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 SELECT per classificar, a l'ORDER BY per imposar un ordre de negoci que l'alfabètic no pot donar, i al GROUP BY per 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 modern FILTER. I saps triar entre COALESCE, NULLIF, CASE i FILTER segons 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

Mòdul 2: Consultes bàsiques de SQL

Mòdul 3: Treballar amb múltiples taules

Mòdul 4: Filtratge avançat de dades

Mòdul 5: Manipulació de dades

Mòdul 6: Funcions avançades de SQL

Mòdul 7: Subconsultes i consultes imbricades

Mòdul 8: Índexs i optimització del rendiment

Mòdul 9: Transaccions i concurrència

Mòdul 10: Temes avançats

Mòdul 11: SQL a la pràctica

Mòdul 12: Projecte final

© Copyright 2026. Tots els drets reservats