UPDATE modifica files que ja existeixen. És la instrucció més perillosa que has vist fins ara, i la raó és aritmètica: si t'equivoques en un INSERT crees una fila de més i l'esborres; si t'equivoques en un UPDATE sobreescrius dades correctes amb dades incorrectes i el valor anterior deixa d'existir. No hi ha paperera. No hi ha "desfés". Només hi ha la còpia de seguretat d'ahir a la nit, si és que n'hi ha.

Per això aquesta lliçó dedica la seva primera meitat a una cosa que no és sintaxi: el protocol professional per executar un UPDATE sense destrossar res. Escriure primer el SELECT, comptar les files, embolcallar-ho en una transacció, comprovar i només llavors confirmar. És un hàbit de trenta segons que separa qui porta anys tocant bases de dades de qui en porta un mes. Després vindrà tot el que UPDATE sap fer: diverses columnes alhora, càlculs sobre el valor anterior, actualitzacions a partir d'una altra taula amb FROM, RETURNING per veure exactament què has canviat, i la propietat —la idempotència— que determina si un script es pot reexecutar sense por.

⚠️ Avís de seguretat

UPDATE és una operació destructiva: substitueix dades existents sense conservar el valor anterior.

  • Executa tots els exemples d'aquesta lliçó sobre la teva base de dades de pràctiques (botigaverda), mai sobre un sistema en producció.
  • Fes una còpia de seguretat prèvia abans de qualsevol prova: pg_dump -U curs_sql -d botigaverda -f copia.sql. Recuperar l'estat inicial és tan fàcil com tornar a llançar botigaverda.sql.
  • En un sistema real, un UPDATE sobre dades vives ha d'anar revisat per una altra persona i executat dins d'una transacció.

Contingut

  1. La sintaxi, i el WHERE com a xarxa de seguretat
  2. Què passa exactament si oblides el WHERE
  3. El protocol dels cinc passos
  4. BEGINROLLBACK / COMMIT: la xarxa de debò
  5. Actualitzar diverses columnes alhora
  6. Calcular sobre el valor anterior: pujar preus un 5 %
  7. Per què l'ordre de les assignacions no importa
  8. UPDATE ... FROM: actualitzar a partir d'una altra taula
  9. RETURNING: veure què has canviat
  10. UPDATE que viola una restricció
  11. Actualitzar a NULL, i l'efecte d'ON UPDATE CASCADE
  12. Idempotència: per què importa en reexecutar un script
  13. Quatre casos de negoci de BotigaVerda
  14. Errors habituals i consells
  15. Exercicis
  16. Conclusió

  1. La sintaxi, i el WHERE com a xarxa de seguretat

UPDATE taula
SET    columna1 = valor1,
       columna2 = valor2,
       ...
WHERE  condició;

Tres peces: quina taula, quines columnes canvien i a quin valor, i quines files. Només la tercera és opcional per al motor; cap ho és per a tu.

Un exemple mínim: el producte 13 (Espelmes de cera de soja) porta mesos amb estoc 0 i es decideix descatalogar-lo.

UPDATE productes
SET    actiu = FALSE
WHERE  id = 13;
UPDATE 1

UPDATE N et diu quantes files ha modificat. Aquell número és la teva primera línia de defensa: si esperaves una fila i hi diu UPDATE 17, alguna cosa ha anat molt malament — però almenys ho saps.

Un matís sobre aquell comptador: PostgreSQL compta les files que complien el WHERE, no les que han canviat de valor. Si actualitzes una columna al mateix valor que ja tenia, la fila compta igual:

UPDATE productes
SET    stock = 0
WHERE  id = 13;
UPDATE 1

Una fila "modificada" encara que l'stock ja fos 0. És important saber-ho: UPDATE 1 significa "una fila complia la condició", no "una fila ha canviat".

I el punt crític, ja anunciat al final del mòdul 4: en l'ordre lògic d'execució, el WHERE d'un UPDATE fa exactament la mateixa feina que el d'un SELECT. Determina el conjunt de files afectades.

flowchart LR
    A["1 · FROM<br/>la taula (i el FROM extra)"] --> B["2 · WHERE<br/>selecciona les FILES<br/>que es tocaran"]
    B --> C["3 · SET<br/>calcula els valors nous<br/>a partir dels antics"]
    C --> D["4 · restriccions<br/>NOT NULL · CHECK · UNIQUE · FK"]
    D --> E["5 · RETURNING<br/>(opcional)"]

Aquesta és la raó que el mòdul 5 sigui la continuació natural del 4: el WHERE que portes quatre mòduls afinant és el mateix. El que canvia és la conseqüència d'equivocar-se.

  1. Què passa exactament si oblides el WHERE

No hi ha misteri: l'UPDATE s'aplica a totes les files de la taula.

-- ⚠️ INCORRECTA: no facis això fora d'una transacció
UPDATE productes
SET    preu = preu * 1.05;
UPDATE 20

Els vint productes del catàleg acaben de pujar un 5 %. No només els de Begudes: tots. I el preu anterior ja no és enlloc.

SELECT id, nom, preu FROM productes ORDER BY id LIMIT 5;
id nom preu
1 Oli d'oliva verge extra 500 ml 13.13
2 Arròs integral ecològic 1 kg 4.10
3 Mel de tarongina crua 500 g 10.24
4 Pasta d'espelta 500 g 2.94
5 Tomàquet triturat ecològic 400 g 2.05

(5 primeres de 20 files.)

I aquí hi ha el veritablement insidiós: PostgreSQL no ha protestat. No hi ha error, no hi ha avís, no hi ha confirmació. La sentència és sintàcticament perfecta i semànticament legal. El motor ha fet exactament el que li has demanat. L'únic indici és aquell UPDATE 20 en lloc d'UPDATE 4, i per veure'l cal estar mirant.

Ara imagina't la mateixa sentència sobre una taula productes de 40 000 referències en una botiga real, a les set de la tarda d'un divendres. No és una història inventada: és un dels incidents més repetits de la indústria.

Per què SQL no et protegeix. Alguns clients gràfics avisen si detecten un UPDATE o un DELETE sense WHERE, i psql té fins i tot una opció per a això (\set ON_ERROR_STOP on no fa això, però eines com pgcli o DBeaver sí que ho fan). Però l'estàndard SQL no ho contempla: un UPDATE sense WHERE és una operació legítima —de vegades és just el que vols, per exemple en omplir una columna nova—. La protecció l'ha de posar el teu procés, no el llenguatge.

  1. El protocol dels cinc passos

Aquest és l'hàbit. Cinc passos, trenta segons, zero incidents.

L'encàrrec: "puja un 5 % el preu de tots els productes de Begudes".

Pas 1 — Escriu el SELECT amb el WHERE definitiu

Abans d'escriure la paraula UPDATE, escriu la consulta que localitza exactament les files que vols tocar.

SELECT id, nom, categoria_id, preu
FROM   productes
WHERE  categoria_id = 4
ORDER BY id;
id nom categoria_id preu
14 Infusió de camamilla ecològica 20 u 4 3.25
15 Te verd matcha cerimonial 30 g 4 22.00
16 Kombutxa de gingebre 750 ml 4 4.95
17 Suc de taronja premsat en fred 1 L 4 5.40

Pas 2 — Compta les files i compara amb el que esperaves

SELECT COUNT(*) AS files_afectades
FROM   productes
WHERE  categoria_id = 4;
files_afectades
4

Quatre. Coincideix amb l'esperat (Begudes té 4 productes, com vas comprovar a 04-06). Si el número et sorprèn, atura't. Aquell astorament és el senyal més valuós que rebràs.

Pas 3 — Previsualitza els valors nous

Escriu l'expressió del SET com una columna calculada del SELECT i mira-te'ls:

SELECT id,
       nom,
       preu                    AS preu_actual,
       ROUND(preu * 1.05, 2)   AS preu_nou,
       ROUND(preu * 0.05, 2)   AS pujada
FROM   productes
WHERE  categoria_id = 4
ORDER BY id;
id nom preu_actual preu_nou pujada
14 Infusió de camamilla ecològica 20 u 3.25 3.41 0.16
15 Te verd matcha cerimonial 30 g 22.00 23.10 1.10
16 Kombutxa de gingebre 750 ml 4.95 5.20 0.25
17 Suc de taronja premsat en fred 1 L 5.40 5.67 0.27

Tots els preus són raonables. Cap arrodoniment estrany, cap negatiu, cap sorpresa.

Pas 4 — Converteix el SELECT en UPDATE sense tocar el WHERE

Copia el WHERE literalment. No el reescriguis de memòria: copia'l.

UPDATE productes
SET    preu = preu * 1.05
WHERE  categoria_id = 4;
UPDATE 4

Quatre, el mateix número del pas 2. Si hi hagués posat UPDATE 20, sabries a l'instant que el WHERE s'ha perdut pel camí.

Pas 5 — Verifica

SELECT id, nom, preu
FROM   productes
WHERE  categoria_id = 4
ORDER BY id;
id nom preu
14 Infusió de camamilla ecològica 20 u 3.41
15 Te verd matcha cerimonial 30 g 23.10
16 Kombutxa de gingebre 750 ml 5.20
17 Suc de taronja premsat en fred 1 L 5.67

Idèntics a la previsualització del pas 3.

Detall de tipus que convé entendre. No hem escrit ROUND(...) a l'UPDATE i tot i així els preus han quedat amb dos decimals. El motiu és que preu és NUMERIC(10,2): en assignar 3.25 * 1.05 = 3.4125 a aquella columna, PostgreSQL arrodoneix en emmagatzemar (3.41). Funciona, però és arrodoniment implícit. Si el resultat t'importa —i amb diners sempre importa—, escriu el ROUND explícit perquè el lector del teu codi sàpiga que la decisió va ser teva i no del sistema de tipus.

  1. BEGINROLLBACK / COMMIT: la xarxa de debò

El protocol de l'apartat anterior redueix moltíssim el risc, però no l'elimina. La xarxa de seguretat completa és la transacció.

A PostgreSQL, cada sentència solta s'executa i es confirma sola (autocommit). Amb BEGIN obres un bloc on res no és definitiu fins que diguis COMMIT, i on ROLLBACK ho desfà tot:

BEGIN;

UPDATE productes
SET    preu = preu * 1.05
WHERE  categoria_id = 4;
BEGIN
UPDATE 4

Ara, abans de confirmar, verifica:

SELECT id, nom, preu FROM productes WHERE categoria_id = 4 ORDER BY id;
id nom preu
14 Infusió de camamilla ecològica 20 u 3.41
15 Te verd matcha cerimonial 30 g 23.10
16 Kombutxa de gingebre 750 ml 5.20
17 Suc de taronja premsat en fred 1 L 5.67

Si està bé:

COMMIT;
COMMIT

I si està malament —o si aquell UPDATE 4 hagués estat un UPDATE 20—:

ROLLBACK;
ROLLBACK
SELECT id, nom, preu FROM productes WHERE categoria_id = 4 ORDER BY id;
id nom preu
14 Infusió de camamilla ecològica 20 u 3.25
15 Te verd matcha cerimonial 30 g 22.00
16 Kombutxa de gingebre 750 ml 4.95
17 Suc de taronja premsat en fred 1 L 5.40

Com si no hagués passat mai. Aquest és el poder que portes quatre mòduls sense fer servir.

El flux complet, que a partir d'aquí hauries d'aplicar sempre que toquis dades:

flowchart TD
    A["SELECT amb el WHERE<br/>i comptar files"] --> B{"El número<br/>és l'esperat?"}
    B -->|No| A
    B -->|Sí| C["BEGIN"]
    C --> D["UPDATE"]
    D --> E{"UPDATE N coincideix<br/>amb el recompte?"}
    E -->|No| F["ROLLBACK"]
    E -->|Sí| G["SELECT de verificació"]
    G --> H{"Les dades<br/>són correctes?"}
    H -->|No| F
    H -->|Sí| I["COMMIT"]
    F --> A

Tres advertiments pràctics sobre les transaccions, el just per fer-les servir avui:

Advertiment Detall
Una transacció oberta bloqueja files Mentre no facis COMMIT o ROLLBACK, les files que has tocat queden bloquejades per a altres sessions. No te'n vagis a dinar amb un BEGIN obert
Un error avorta la transacció sencera Si una sentència falla dins del bloc, PostgreSQL entra en estat 25P02 i rebutja tot fins que facis ROLLBACK. El missatge és current transaction is aborted, commands ignored until end of transaction block
Les seqüències no es desfan Com vas veure a 05-02, un ROLLBACK no retorna els id consumits

Això és només la punta. El model complet de les transaccions —les propietats ACID, els nivells d'aïllament, els bloqueigs i els interbloqueigs— és el mòdul 9 sencer. Aquí ho fas servir com a eina de treball: obre, comprova, confirma o desfés. Amb això ja ets molt més segur del que eres fa deu minuts.

I una alternativa quan la transacció no n'hi ha prou, perquè el canvi és gran o irreversible: fes una còpia de la taula abans.

CREATE TABLE productes_backup_20260302 AS
SELECT * FROM productes;
SELECT 20

És l'ús legítim de CTAS que anunciava 05-01. Recorda que no copia restriccions (05-01, apartat 8): serveix per restaurar valors, no per substituir la taula.

  1. Actualitzar diverses columnes alhora

Se separen per comes dins del mateix SET:

UPDATE productes
SET    actiu = FALSE,
       stock = 0
WHERE  id = 20;
UPDATE 1
SELECT id, nom, preu, stock, actiu FROM productes WHERE id = 20;
id nom preu stock actiu
20 Càpsules d'espirulina 120 u 16.40 0 false

Un sol UPDATE amb dues assignacions és sempre millor que dos UPDATE, i no només per escriure menys:

Dos UPDATE separats Un amb dues assignacions
Recorreguts de la taula 2 1
Versions de fila creades 2 1
Estat intermedi visible Sí: entre tots dos, la fila està a mitges No
Atomicitat sense BEGIN No garantida Garantida

Aquell "estat intermedi visible" és l'argument de pes: entre el primer UPDATE i el segon existeix un instant en què el producte està inactiu però amb 55 unitats d'estoc. Una altra sessió pot llegir-lo just aquí.

  1. Calcular sobre el valor anterior: pujar preus un 5 %

Al costat dret de l'= pots fer servir qualsevol expressió, incloses les columnes de la mateixa fila:

UPDATE productes SET preu  = preu * 1.05 WHERE categoria_id = 4;            -- pujar un 5 %
UPDATE productes SET stock = stock + 50  WHERE id = 13;                     -- reposar 50 unitats
UPDATE comandes  SET despeses_enviament = 0 WHERE despeses_enviament < 5;   -- ports gratis

La regla, i és la clau de tot l'apartat següent: el costat dret s'avalua amb els valors que la fila tenia ABANS de l'UPDATE.

Un exemple amb CASE… bé, amb CASE encara no (mòdul 6). Un exemple amb aritmètica i una columna d'una altra fila de la mateixa taula tampoc: això és una subconsulta (mòdul 7). Amb el que tens avui, les expressions útils són les de 02-02: aritmètica, concatenació i funcions bàsiques.

UPDATE productes
SET    preu = ROUND(cost * 2.2, 2)
WHERE  categoria_id = 5
  AND  cost IS NOT NULL;
UPDATE 2
SELECT id, nom, cost, preu, ROUND((preu - cost) / preu, 4) AS marge_relatiu
FROM productes WHERE categoria_id = 5 ORDER BY id;
id nom cost preu marge_relatiu
18 Raspall de dents de bambú 1.20 2.64 0.5455
19 Desodorant natural en barra 50 g 3.30 7.26 0.5455

El marge relatiu surt idèntic en tots dos, i no és casualitat: en fixar el preu com a cost * 2.2, el marge relatiu és sempre 1 − 1/2,2 = 0,5455, independentment del cost. És la comprovació que la fórmula fa el que es pretenia.

Fixa't en l'AND cost IS NOT NULL. A BotigaVerda tots els productes tenen cost, però la columna és nul·lable (05-01): sense aquell filtre, un producte sense cost hauria rebut preu = NULL, i com que preu és NOT NULL la sentència sencera hauria fallat. Quan el SET fa aritmètica amb una columna nul·lable, el WHERE t'ha de protegir — és la lògica de tres valors de 04-03 aplicada a l'escriptura.

  1. Per què l'ordre de les assignacions no importa

Aquesta és la diferència més profunda entre UPDATE i un llenguatge imperatiu, i sorprèn tothom que arriba des de Java, Python o C.

UPDATE productes
SET    preu = cost * 2,
       cost = cost * 1.1
WHERE  id = 5;
UPDATE 1

En Python, preu = cost * 2 seguit de cost = cost * 1.1 faria servir el cost original a la primera línia i el modificaria a la segona. En SQL passa el mateix, però per una raó diferent i més forta: no és que les assignacions s'executin en ordre, és que totes s'avaluen simultàniament sobre la fila anterior.

El producte 5 tenia preu = 1.95 i cost = 0.90:

SELECT id, nom, preu, cost FROM productes WHERE id = 5;
id nom preu cost
5 Tomàquet triturat ecològic 400 g 1.80 0.99

preu = 0,90 × 2 = 1,80 (amb el cost antic, no amb 0,99). cost = 0,90 × 1,1 = 0,99.

I ara la prova definitiva, que en un llenguatge imperatiu necessitaria una variable temporal:

-- Intercanviar dues columnes: en SQL funciona directament
UPDATE productes
SET    preu = cost,
       cost = preu
WHERE  id = 3;
UPDATE 1
SELECT id, nom, preu, cost FROM productes WHERE id = 3;
id nom preu cost
3 Mel de tarongina crua 500 g 5.40 9.75

Estaven a 9,75 i 5,40; ara estan a 5,40 i 9,75. S'han intercanviat. En Python hauries necessitat tmp = preu o una assignació múltiple; en SQL és el comportament per defecte.

Tres conseqüències pràctiques:

  1. Pots escriure les assignacions en qualsevol ordre. SET a = ..., b = ... i SET b = ..., a = ... són equivalents.
  2. No pots encadenar càlculs dins del mateix UPDATE. SET preu = preu * 1.1, iva = preu * 0.21 calcularia l'IVA sobre el preu vell, no sobre el que acaba de pujar. Si necessites encadenar, calen dos UPDATE (dins de la mateixa transacció).
  3. No pots assignar dues vegades la mateixa columna. PostgreSQL ho rebutja:
-- ⚠️ INCORRECTA
UPDATE productes SET preu = preu * 1.05, preu = preu + 1 WHERE id = 1;
ERROR:  multiple assignments to same column "preu"

  1. UPDATE ... FROM: actualitzar a partir d'una altra taula

Fins ara, els valors nous sortien de la mateixa fila. Sovint surten d'una altra taula. PostgreSQL ho resol amb una clàusula FROM a l'UPDATE, que funciona igual que el FROM d'un SELECT: aporta taules addicionals i es relaciona amb la que actualitzes mitjançant el WHERE.

UPDATE taula_desti AS d
SET    columna = expressió_que_fa_servir_a
FROM   altra_taula AS a
WHERE  d.clau = a.clau
  AND  altres_condicions;

Cas 1: descomptar l'estoc d'una comanda

Reprenem la comanda 21 que vam registrar a 05-02 (client 6, tres línies: 2 olis, 1 mel, 3 infusions). Falta descomptar l'estoc:

-- Abans
SELECT id, nom, stock FROM productes WHERE id IN (1, 3, 14) ORDER BY id;
id nom stock
1 Oli d'oliva verge extra 500 ml 120
3 Mel de tarongina crua 500 g 80
14 Infusió de camamilla ecològica 20 u 180
UPDATE productes       AS p
SET    stock = p.stock - lc.quantitat
FROM   linies_comanda  AS lc
WHERE  lc.producte_id = p.id
  AND  lc.comanda_id  = 21;
UPDATE 3
SELECT id, nom, stock FROM productes WHERE id IN (1, 3, 14) ORDER BY id;
id nom stock
1 Oli d'oliva verge extra 500 ml 118
3 Mel de tarongina crua 500 g 79
14 Infusió de camamilla ecològica 20 u 177

120 − 2, 80 − 1, 180 − 3. Exacte.

⚠️ La trampa mortal d'UPDATE ... FROM. Si una fila de productes aparella amb diverses files de linies_comanda, PostgreSQL no suma: tria una de les coincidències, de manera no determinista, i aplica aquella. No dóna error, no avisa. Si la comanda 21 tingués dues línies del mateix producte, aquest UPDATE en descomptaria només una.

És exactament la multiplicació de files del mòdul 3, però molt més perillosa: en un SELECT la veus perquè surten files de més; aquí és invisible. La solució correcta passa per agregar primer (SUM(lc.quantitat) agrupat per producte) i actualitzar contra aquell resultat, cosa que requereix una subconsulta al FROM — mòdul 7. Mentrestant: abans d'un UPDATE ... FROM, comprova sempre que l'aparellament és 1:1.

Aquella comprovació es fa així:

SELECT lc.producte_id, COUNT(*) AS linies
FROM   linies_comanda AS lc
WHERE  lc.comanda_id = 21
GROUP BY lc.producte_id
HAVING COUNT(*) > 1;
(0 files)

Zero files: no hi ha productes repetits i l'aparellament és 1:1. Ara sí.

Cas 2: actualitzar preus de comandes encara no enviades

Acabem de pujar un 5 % les Begudes (apartat 3). Les comandes que ja es van enviar o lliurar han de conservar el seu preu històric —aquesta és tota la raó de ser de preu_unitari (01-05)—, però les que encara no han sortit han de facturar la tarifa nova.

-- Pas 1: veure què es tocarà
SELECT lc.id, lc.comanda_id, co.estat, p.nom,
       lc.preu_unitari AS preu_linia,
       p.preu          AS preu_cataleg
FROM   linies_comanda AS lc
JOIN   comandes       AS co ON lc.comanda_id  = co.id
JOIN   productes      AS p  ON lc.producte_id = p.id
WHERE  co.estat IN ('pendent', 'pagat')
  AND  lc.preu_unitari <> p.preu
ORDER BY lc.id;
id comanda_id estat nom preu_linia preu_cataleg
45 19 pagat Kombutxa de gingebre 750 ml 4.95 5.20

Una sola línia. La resta de línies de les comandes 18, 19 i 20 ja coincideix amb el catàleg.

-- Pas 2: l'UPDATE
UPDATE linies_comanda AS lc
SET    preu_unitari = p.preu
FROM   productes AS p,
       comandes  AS co
WHERE  lc.producte_id = p.id
  AND  lc.comanda_id  = co.id
  AND  co.estat IN ('pendent', 'pagat')
  AND  lc.preu_unitari <> p.preu
RETURNING lc.id, lc.comanda_id, lc.producte_id, lc.preu_unitari;
id comanda_id producte_id preu_unitari
45 19 16 5.20
UPDATE 1

Fixa't en dues coses. Primera: al FROM d'un UPDATE pots posar diverses taules, separades per comes o amb JOIN entre elles. Segona: la taula que s'actualitza (linies_comanda) no es repeteix al FROM. Si la hi posessis, PostgreSQL la tractaria com una segona instància independent i el resultat seria un producte cartesià silenciós.

Equivalències per motor

Aquesta és una de les divergències més grans del mòdul:

Motor Sintaxi
PostgreSQL UPDATE d SET c = a.c FROM altra a WHERE d.k = a.k
MySQL / MariaDB UPDATE d JOIN altra a ON d.k = a.k SET d.c = a.c — el JOIN va abans del SET
SQL Server UPDATE d SET c = a.c FROM desti d JOIN altra a ON d.k = a.k — la taula destinació que es repeteix al FROM
SQLite UPDATE d SET c = (...) FROM altra a WHERE d.k = a.k des de la versió 3.33; abans, subconsulta correlacionada
Oracle No té UPDATE ... FROM. Es fa servir MERGE (05-05) o una subconsulta correlacionada

I l'estàndard SQL, en realitat, no defineix cap de les quatre: la forma portable és una subconsulta correlacionada al SET, que veuràs al mòdul 7.

  1. RETURNING: veure què has canviat

Igual que a INSERT, RETURNING retorna les files afectades. A UPDATE és encara més útil, perquè t'ensenya el resultat:

UPDATE comandes
SET    estat = 'lliurat'
WHERE  estat = 'enviat'
  AND  data_comanda < DATE '2026-02-01'
RETURNING id, client_id, data_comanda, estat, metode_pagament;
id client_id data_comanda estat metode_pagament
16 4 2025-12-19 lliurat targeta
17 7 2026-01-13 lliurat paypal
UPDATE 2

Dues comandes que portaven mesos "enviat" passen a "lliurat". I ho veus sense necessitat d'un SELECT posterior.

El que RETURNING no pot fer: retornar el valor anterior. Només veu la fila ja actualitzada. Si necessites conservar el valor vell, tens tres opcions: copiar la taula abans (apartat 4), un INSERT ... SELECT a una taula d'auditoria abans de l'UPDATE (05-02), o un trigger d'auditoria (mòdul 10).

Nota de dialecte: SQL Server sí que pot: la seva clàusula OUTPUT DELETED.columna, INSERTED.columna retorna l'abans i el després a la mateixa sentència. És una de les poques coses en què la seva sintaxi supera la de PostgreSQL.

  1. UPDATE que viola una restricció

Un UPDATE està subjecte a exactament les mateixes restriccions que un INSERT, i dóna els mateixos errors. La diferència és que ara la fila ja existia i era correcta.

CHECK:

UPDATE productes SET stock = stock - 1 WHERE id = 13;
ERROR:  new row for relation "productes" violates check constraint "productes_stock_check"
DETAIL:  Failing row contains (13, Espelmes de cera de soja (pack 2), 3, 5, 13.75, 6.90, -1, t, 2025-03-01).

El producte 13 tenia estoc 0 i CHECK (stock >= 0) impedeix baixar-lo a −1. Aquell CHECK, que a 05-01 semblava burocràcia, acaba d'impedir un estoc negatiu.

CHECK de domini:

UPDATE comandes SET estat = 'retornat' WHERE id = 6;
ERROR:  new row for relation "comandes" violates check constraint "comandes_estat_check"
DETAIL:  Failing row contains (6, 5, 4, 2025-05-23, retornat, targeta, 4.95).

UNIQUE:

UPDATE clients SET email = '[email protected]' WHERE id = 2;
ERROR:  duplicate key value violates unique constraint "clients_email_key"
DETAIL:  Key (email)=([email protected]) already exists.

FOREIGN KEY:

UPDATE comandes SET client_id = 999 WHERE id = 1;
ERROR:  insert or update on table "comandes" violates foreign key constraint "comandes_client_id_fkey"
DETAIL:  Key (client_id)=(999) is not present in table "clients".

NOT NULL:

UPDATE comandes SET client_id = NULL WHERE id = 1;
ERROR:  null value in column "client_id" of relation "comandes" violates not-null constraint
DETAIL:  Failing row contains (1, null, null, 2025-03-04, lliurat, targeta, 4.95).

En els cinc casos, la fila original queda intacta: un UPDATE que falla no modifica res. I com a l'INSERT, si l'UPDATE afectava deu files i una viola una restricció, no se n'actualitza cap.

  1. Actualitzar a NULL, i l'efecte d'ON UPDATE CASCADE

Posar una columna a NULL

Es fa amb = NULL, no amb IS NULL (que és un operador de comparació, 04-03):

UPDATE comandes
SET    empleat_id = NULL
WHERE  id = 2
RETURNING id, client_id, empleat_id, data_comanda, estat;
id client_id empleat_id data_comanda estat
2 2 (null) 2025-03-12 lliurat
UPDATE 1

La comanda 2 deixa de tenir comercial assignat: passa a comportar-se com les deu comandes web. Ara hi hauria 11 comandes amb empleat_id IS NULL.

Només ho pots fer si la columna admet nuls. I un advertiment semàntic que arrossega 04-03: NULL significa "desconegut", no "cap". Posar empleat_id a NULL per significar "ho va gestionar el web" és correcte perquè l'esquema defineix aquell nul així. Posar salari = NULL per significar "cobra 0" seria un error de disseny: 0 i "no ho sé" són coses diferents.

ON UPDATE CASCADE

Quan canvia el valor de la clau primària d'un pare, ON UPDATE decideix què passa amb els fills. BotigaVerda no ho fa servir perquè les seves PK són subrogades i no canvien mai (01-05). Amb una taula d'exemple es veu bé:

-- Exemple puntual: taules amb clau natural, fora de l'esquema de BotigaVerda
CREATE TABLE paisos (
    codi VARCHAR(3) PRIMARY KEY,
    nom  VARCHAR(60) NOT NULL
);

CREATE TABLE tarifes_enviament (
    id          INTEGER       GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    pais_codi   VARCHAR(3)    NOT NULL REFERENCES paisos(codi)
                ON UPDATE CASCADE ON DELETE RESTRICT,
    pes_max_kg  NUMERIC(5,2)  NOT NULL,
    import      NUMERIC(10,2) NOT NULL
);

INSERT INTO paisos (codi, nom) VALUES ('ESP', 'Espanya'), ('PRT', 'Portugal'), ('FRA', 'França');
INSERT INTO tarifes_enviament (pais_codi, pes_max_kg, import) VALUES
('ESP', 2.00, 4.95), ('ESP', 10.00, 6.50), ('PRT', 2.00, 9.90), ('FRA', 2.00, 12.50);
CREATE TABLE
CREATE TABLE
INSERT 0 3
INSERT 0 4

Ara l'empresa decideix migrar a codis ISO de dues lletres:

UPDATE paisos SET codi = 'ES' WHERE codi = 'ESP';
UPDATE 1
SELECT id, pais_codi, pes_max_kg, import FROM tarifes_enviament ORDER BY id;
id pais_codi pes_max_kg import
1 ES 2.00 4.95
2 ES 10.00 6.50
3 PRT 2.00 9.90
4 FRA 2.00 12.50

Les dues files filles s'han actualitzat soles. Sense ON UPDATE CASCADE, l'UPDATE hauria fallat:

ERROR:  update or delete on table "paisos" violates foreign key constraint "tarifes_enviament_pais_codi_fkey" on table "tarifes_enviament"
DETAIL:  Key (codi)=(ESP) is still referenced from table "tarifes_enviament".

I aquí hi ha l'argument de 01-05 en la seva forma més nítida: amb claus subrogades aquest problema no existeix. ON UPDATE CASCADE és la resposta a una pregunta que només es fa qui va triar una clau natural mutable.

  1. Idempotència: per què importa en reexecutar un script

Una operació és idempotent si executar-la dues vegades dóna el mateix resultat que executar-la una. És una propietat crucial en qualsevol procés automatitzat, perquè tard o d'hora un script s'executa dues vegades: falla a mitges i es rellança, es desplega dues vegades, algú no està segur de si va arribar a córrer.

Compara:

-- ❌ NO idempotent
UPDATE productes SET preu = preu * 1.05 WHERE categoria_id = 4;

-- ✅ Idempotent
UPDATE productes SET preu = 3.41 WHERE id = 14;

Amb el primer, sobre el preu original de 3,25 €:

Execució preu resultant
1a 3.41
2a 3.58
3a 3.76

El preu es dispara sense que ningú no se n'adoni. Amb el segon, el resultat és 3,41 € les tres vegades.

La regla que se'n dedueix:

Forma del SET Idempotent? Per què
SET col = valor_fix Fixa un estat absolut
SET col = altra_columna L'origen no canvia
SET col = col * k, col + k, col - k No Cada execució parteix del resultat anterior
SET col = col + 1 (comptadors) No És just el contrari del que es busca

I les tres formes de conviure amb un UPDATE no idempotent quan el càlcul relatiu és el que necessites:

  1. Fer-lo idempotent amb el WHERE. Si pots identificar les files "encara no processades", la segona passada no troba res:
UPDATE comandes
SET    estat = 'lliurat'
WHERE  estat = 'enviat'
  AND  data_comanda < DATE '2026-02-01';

La primera execució retorna UPDATE 2; la segona, UPDATE 0, perquè ja no queda cap comanda en estat enviat amb aquella data. El WHERE ben triat converteix una operació relativa en una d'idempotent. És el patró més important d'aquest apartat.

  1. Registrar que ja s'ha fet, en una taula de control o amb una columna de marca, i comprovar-ho abans.

  2. Embolcallar-lo en una migració versionada, que per definició s'aplica una sola vegada. És el tema de 05-06.

Consell d'or: quan escriguis un script que s'hagi d'executar sense supervisió, pregunta't sempre "què passa si això corre dues vegades?". Si la resposta és "un desastre silenciós", reescriu-lo.

  1. Quatre casos de negoci de BotigaVerda

Tots sobre la base acabada de recarregar, amb el protocol complet.

13.1. Pujar el preu d'una categoria

Ja resolt a l'apartat 3, amb el seu SELECT previ, el seu recompte de 4 files, la seva previsualització i la seva verificació. És el cas canònic.

13.2. Marcar com a lliurades les comandes enviades fa més d'un mes

BEGIN;

SELECT COUNT(*) AS afectats
FROM   comandes
WHERE  estat = 'enviat'
  AND  data_comanda < DATE '2026-02-01';
afectats
2
UPDATE comandes
SET    estat = 'lliurat'
WHERE  estat = 'enviat'
  AND  data_comanda < DATE '2026-02-01'
RETURNING id, client_id, data_comanda, estat;
id client_id data_comanda estat
16 4 2025-12-19 lliurat
17 7 2026-01-13 lliurat
UPDATE 2
SELECT estat, COUNT(*) AS comandes FROM comandes GROUP BY estat ORDER BY comandes DESC, estat;
estat comandes
lliurat 16
pagat 2
cancellat 1
pendent 1
COMMIT;

De 14 lliurades a 16, i l'estat enviat desapareix. Coincideix amb les 2 files anunciades. Observa l'ús del rang de dates tancat per baix (< DATE '2026-02-01') seguint la convenció del curs.

13.3. Corregir un email mal escrit

El client 8 avisa que la seva adreça està malament:

SELECT id, nom, cognoms, email, pais FROM clients WHERE id = 8;
id nom cognoms email pais
8 Tiago Almeida Nunes [email protected] Portugal
UPDATE clients
SET    email = '[email protected]'
WHERE  id = 8
RETURNING id, nom, cognoms, email;
id nom cognoms email
8 Tiago Almeida Nunes [email protected]
UPDATE 1

Correcció d'una sola fila, l'operació més freqüent de totes. Dues observacions:

  • Es filtra per id, no per email. Filtrar pel valor que canviaràs funciona, però si hi hagués una errada al WHERE no tocaries cap fila (UPDATE 0) i podries creure que ja estava bé. La PK és sempre el filtre més segur.
  • El UNIQUE continua vigilant. Si l'adreça nova ja pertanyés a un altre client, l'UPDATE fallaria amb duplicate key value violates unique constraint "clients_email_key".

13.4. Descatalogar un producte: esborrat lògic

El producte 13 (Espelmes de cera de soja) no s'ha venut mai i porta mesos a zero:

UPDATE productes
SET    actiu = FALSE,
       stock = 0
WHERE  id = 13
RETURNING id, nom, preu, stock, actiu;
id nom preu stock actiu
13 Espelmes de cera de soja (pack 2) 13.75 0 false
UPDATE 1

Això és un esborrat lògic, i és l'alternativa correcta a DELETE FROM productes WHERE id = 13. El producte desapareix del catàleg de venda però conserva la seva fila, el seu històric i la seva integritat referencial. La columna actiu existeix exactament per a això, i el tema té desenvolupament complet a la lliçó següent.

El que cal recordar avui: a partir d'ara, tota consulta de catàleg necessita WHERE actiu:

SELECT COUNT(*) FILTER (WHERE actiu)     AS en_venda,
       COUNT(*) FILTER (WHERE NOT actiu) AS descatalogats,
       COUNT(*)                          AS total
FROM productes;
en_venda descatalogats total
18 2 20

Divuit a la venda (el 13 acaba de sumar-se al 20, que ja estava inactiu). Aquell FILTER és el de 04-04, ara útil per auditar el resultat d'una escriptura.

Errors habituals i consells

  • Oblidar el WHERE. Modifica totes les files sense previ avís. És l'error emblemàtic del mòdul i el motiu del protocol de cinc passos.
  • Reescriure el WHERE de memòria en passar del SELECT a l'UPDATE. Copia'l literalment. La meitat dels incidents venen d'una condició que "era gairebé la mateixa".
  • No mirar l'UPDATE N. És l'únic avís que rebràs. Compara'l sempre amb el recompte previ.
  • Treballar sense BEGIN. En autocommit no hi ha marxa enrere. Un BEGIN costa sis lletres.
  • Deixar una transacció oberta. Bloqueja files per a la resta de sessions. Confirma o desfés abans d'aixecar-te.
  • Esperar que RETURNING retorni el valor anterior. Només veu el nou. Per a l'abans, copia la taula o audita.
  • Encadenar càlculs en un mateix SET. SET a = a * 1.1, b = a * 0.21 fa servir l'a vell per calcular b. Si necessites encadenar, dos UPDATE.
  • Fer servir UPDATE ... FROM amb un aparellament 1:N. No suma: tria una coincidència arbitrària i no avisa. Comprova-ho amb un GROUP BY ... HAVING COUNT(*) > 1 abans.
  • Repetir la taula destinació al FROM de PostgreSQL. Produeix un producte cartesià silenciós. A SQL Server és a l'inrevés: cal repetir-la.
  • Aritmètica sobre una columna nul·lable sense protegir-la al WHERE. preu = cost * 2 amb cost nul dóna NULL i peta contra el NOT NULL.
  • Escriure UPDATE no idempotents en scripts automàtics. preu = preu * 1.05 executat dues vegades puja un 10,25 %. Fixa valors absoluts o acota amb el WHERE.
  • Actualitzar una columna que representa un històric. preu_unitari és el preu del moment de la venda. Tocar-lo en comandes ja lliurades falsifica la comptabilitat.
  • Consell: filtra per la clau primària sempre que puguis. És el WHERE més segur que existeix.
  • Consell: previsualitza el SET com a columna calculada. Veure els valors nous abans d'escriure'ls detecta arrodoniments, negatius i nuls inesperats.
  • Consell: per a canvis grans, CREATE TABLE copia AS SELECT * FROM taula primer. Trenta segons que valen per una nit sencera.

Exercicis

Treballa sobre la base acabada de recarregar i fes servir BEGINROLLBACK per no arrossegar canvis d'un exercici al següent.

Exercici 1

Direcció aprova una revisió de tarifes de Cosmètica natural (categoria 2) amb aquestes regles:

  1. Pujar el preu un 8 %, arrodonint a dos decimals.
  2. Pujar el cost un 3 %, arrodonint a dos decimals.
  3. Només ha d'afectar els productes actius.

Aplica el protocol complet: consulta prèvia, recompte, previsualització amb el marge relatiu abans i després, UPDATE dins d'una transacció, verificació i COMMIT. Després respon: ha millorat o ha empitjorat el marge relatiu? Per què?

Exercici 2

Un company et passa aquest UPDATE amb el comentari "vull posar al dia el preu de les línies de totes les comandes que encara no s'han enviat, i em surt un número estrany":

-- ⚠️ INCORRECTA
UPDATE linies_comanda
SET    preu_unitari = productes.preu
FROM   productes, comandes, linies_comanda
WHERE  linies_comanda.producte_id = productes.id
  AND  linies_comanda.comanda_id  = comandes.id
  AND  comandes.estat = 'pendent';
  1. Troba l'error i explica què fa realment aquesta sentència.
  2. Reescriu-la correctament, amb àlies, i digues quantes files hauria de tocar sobre la base acabada de recarregar.
  3. Explica per què aquesta operació seria catastròfica si el WHERE inclogués comandes.estat = 'lliurat'.

Exercici 3

El magatzem ha rebut un lliurament i cal reposar estoc. Les dades vénen en aquesta taula auxiliar:

CREATE TEMP TABLE recepcio_magatzem (
    producte_id INTEGER NOT NULL,
    unitats     INTEGER NOT NULL CHECK (unitats > 0)
);

INSERT INTO recepcio_magatzem (producte_id, unitats) VALUES
(1, 40), (5, 100), (13, 25), (16, 30);
  1. Escriu l'UPDATE ... FROM que suma les unitats rebudes a l'estoc de cada producte.
  2. Comprova abans que l'aparellament és 1:1.
  3. Mostra l'estat abans i després, i explica què li passa al producte 13.
  4. És idempotent aquest UPDATE? Si no ho és, què passaria si el magatzem executés l'script dues vegades per error, i com ho evitaries?

Solucions

Solució 1

-- Pas 1 i 2: quines files i quantes
SELECT id, nom, preu, cost, actiu
FROM   productes
WHERE  categoria_id = 2
  AND  actiu
ORDER BY id;
id nom preu cost actiu
6 Crema facial d'àloe vera 50 ml 18.90 9.50 true
7 Xampú sòlid de romaní 80 g 8.40 3.60 true
8 Oli corporal d'ametlles 200 ml 14.25 7.10 true
9 Bàlsam labial de calèndula 15 ml 4.60 1.80 true

4 files. Els quatre productes de Cosmètica natural estan actius.

-- Pas 3: previsualització
SELECT id,
       nom,
       preu,
       cost,
       ROUND((preu - cost) / preu, 4)                          AS marge_abans,
       ROUND(preu * 1.08, 2)                                   AS preu_nou,
       ROUND(cost * 1.03, 2)                                   AS cost_nou,
       ROUND((ROUND(preu * 1.08, 2) - ROUND(cost * 1.03, 2))
             / ROUND(preu * 1.08, 2), 4)                       AS marge_despres
FROM   productes
WHERE  categoria_id = 2 AND actiu
ORDER BY id;
id nom preu cost marge_abans preu_nou cost_nou marge_despres
6 Crema facial d'àloe vera 50 ml 18.90 9.50 0.4974 20.41 9.79 0.5203
7 Xampú sòlid de romaní 80 g 8.40 3.60 0.5714 9.07 3.71 0.5910
8 Oli corporal d'ametlles 200 ml 14.25 7.10 0.5018 15.39 7.31 0.5250
9 Bàlsam labial de calèndula 15 ml 4.60 1.80 0.6087 4.97 1.85 0.6278
-- Pas 4: l'UPDATE, en transacció
BEGIN;

UPDATE productes
SET    preu = ROUND(preu * 1.08, 2),
       cost = ROUND(cost * 1.03, 2)
WHERE  categoria_id = 2
  AND  actiu
RETURNING id, nom, preu, cost;
id nom preu cost
6 Crema facial d'àloe vera 50 ml 20.41 9.79
7 Xampú sòlid de romaní 80 g 9.07 3.71
8 Oli corporal d'ametlles 200 ml 15.39 7.31
9 Bàlsam labial de calèndula 15 ml 4.97 1.85
UPDATE 4
-- Pas 5: verificar i confirmar
SELECT ROUND(AVG((preu - cost) / preu), 4) AS marge_mitja
FROM   productes WHERE categoria_id = 2 AND actiu;
marge_mitja
0.5660
COMMIT;

El marge relatiu millora en els quatre productes (d'una mitjana de 0,5448 a 0,5660), i la raó és aritmètica: el preu puja un 8 % i el cost només un 3 %, així que la diferència creix més de pressa que el preu. És la comprovació que la revisió de tarifes fa el que direcció pretenia.

Dues decisions de l'enunciat que calia respectar: ROUND explícit (encara que NUMERIC(10,2) arrodoniria igual, escriure-ho fa la intenció visible) i el filtre AND actiu, que aquí no descarta cap fila però blinda la sentència contra un futur producte descatalogat.

Solució 2

1. L'error. linies_comanda apareix dues vegades: una com a taula destinació de l'UPDATE i una altra dins del FROM. PostgreSQL les tracta com dues instàncies independents, així que la del FROM no està correlacionada amb la que s'actualitza. El resultat és un producte cartesià silenciós: la condició linies_comanda.producte_id = productes.id es resol contra la instància del FROM, i l'UPDATE acaba tocant totes les línies de la taula, posant-los un preu arbitrari. D'aquí "el número estrany".

PostgreSQL fins i tot avisa d'una cosa semblant a la documentació, però no dóna error: la sentència és legal.

2. La versió correcta:

-- ✅ CORRECTA
UPDATE linies_comanda AS lc
SET    preu_unitari = p.preu
FROM   productes AS p,
       comandes  AS co
WHERE  lc.producte_id = p.id
  AND  lc.comanda_id  = co.id
  AND  co.estat       = 'pendent'
  AND  lc.preu_unitari <> p.preu
RETURNING lc.id, lc.comanda_id, lc.producte_id, lc.preu_unitari;
UPDATE 0

Zero files sobre la base acabada de recarregar. L'única comanda pendent és la 20, i les seves dues línies (productes 2 i 18, a 3,90 € i 3,50 €) ja coincideixen amb el catàleg. Aquell UPDATE 0 és informació valuosa, no una fallada: confirma que no hi ha res desfasat.

Tres canvis respecte a l'original: s'hi han posat àlies, s'ha tret linies_comanda del FROM, i s'hi ha afegit AND lc.preu_unitari <> p.preu per no tocar files que ja estan bé (cosa que, de passada, fa la sentència idempotent).

3. Per què seria catastròfic amb 'lliurat'. preu_unitari desa el preu del moment de la venda: és la desnormalització deliberada de 01-05 i la base de tota la comptabilitat històrica. Reescriure'l amb el preu actual significaria que les factures emeses fa un any canvien d'import retroactivament.

El dany concret sobre BotigaVerda: les línies 1 i 4 porten preus històrics (11,95 € i 17,50 €) davant dels actuals (12,50 € i 18,90 €). Aquest UPDATE els reescriuria i la facturació total passaria de 727,95 € a una altra xifra diferent, invalidant totes les quantitats publicades als mòduls 3 i 4. I no hi hauria manera de recuperar els valors antics: ja no serien enlloc.

És l'exemple perfecte d'un UPDATE que s'executa sense error, sense avís, i destrueix informació irrecuperable.

Solució 3

-- 2) Comprovar l'aparellament 1:1 ABANS
SELECT producte_id, COUNT(*) AS vegades
FROM   recepcio_magatzem
GROUP BY producte_id
HAVING COUNT(*) > 1;
(0 files)

Cap producte repetit: cada fila de productes aparellarà amb una sola de recepcio_magatzem.

-- 3) Estat abans
SELECT p.id, p.nom, p.stock, r.unitats AS rebudes
FROM   productes AS p
JOIN   recepcio_magatzem AS r ON r.producte_id = p.id
ORDER BY p.id;
id nom stock rebudes
1 Oli d'oliva verge extra 500 ml 120 40
5 Tomàquet triturat ecològic 400 g 300 100
13 Espelmes de cera de soja (pack 2) 0 25
16 Kombutxa de gingebre 750 ml 60 30
-- 1) L'UPDATE
BEGIN;

UPDATE productes AS p
SET    stock = p.stock + r.unitats
FROM   recepcio_magatzem AS r
WHERE  r.producte_id = p.id
RETURNING p.id, p.nom, p.stock;
id nom stock
1 Oli d'oliva verge extra 500 ml 160
5 Tomàquet triturat ecològic 400 g 400
13 Espelmes de cera de soja (pack 2) 25
16 Kombutxa de gingebre 750 ml 90
UPDATE 4
COMMIT;

Què li passa al producte 13. Passa de 0 a 25 unitats: deixa d'estar exhaurit. És un canvi amb conseqüències més enllà de la taula, perquè el producte 13 és un dels "buits deliberats" del conjunt de dades del curs (01-06): tenia estoc 0 i no s'havia venut mai. Si deixes aquest canvi confirmat, alguns exemples de mòduls anteriors deixaran de coincidir. Recarrega l'script abans de continuar.

4. Idempotència. No ho és. SET stock = stock + unitats és una operació relativa: executar-lo dues vegades sumaria les unitats dues vegades, i el magatzem tindria 200 unitats d'oli al sistema i 160 a la prestatgeria. Un descaire d'inventari que ningú no detectaria fins al recompte anual.

Execució stock del producte 1
0 (inicial) 120
1a 160
2a 200 ← incorrecte

Les tres formes d'evitar-ho, de menys a més robusta:

  1. Marcar les recepcions ja processades. Afegir a recepcio_magatzem una columna processada BOOLEAN NOT NULL DEFAULT FALSE, filtrar per WHERE NOT r.processada a l'UPDATE i marcar-la a TRUE a la mateixa transacció. La segona execució no troba res.
  2. Registrar cada moviment en una taula moviments_stock amb la seva pròpia clau única, i calcular l'estoc com la suma dels moviments en lloc de desar-lo com a valor mutable. És l'enfocament de llibre major, el més robust i el que fan servir els sistemes d'inventari seriosos.
  3. Convertir-lo en una migració versionada, que per construcció s'aplica una sola vegada (05-06).

I la propietat que fa que l'opció 1 funcioni: sempre que puguis identificar al WHERE les files "encara no processades", una operació relativa es torna idempotent. És el mateix patró del cas 13.2, on WHERE estat = 'enviat' garanteix que la segona passada retorni UPDATE 0.

Conclusió

UPDATE és la primera instrucció del curs que pot destruir informació, i ja saps manejar-la:

  • La sintaxi UPDATE taula SET col = valor WHERE ..., on el WHERE fa la mateixa feina que en un SELECT però amb conseqüències diferents. UPDATE N compta les files que complien la condició, no les que van canviar de valor.
  • Sense WHERE, es modifiquen totes les files: 20 productes en lloc de 4, sense error, sense avís i sense marxa enrere.
  • El protocol dels cinc passos: SELECT amb el WHERE definitiu → comptar files → previsualitzar els valors nous com a columna calculada → convertir-lo en UPDATE copiant el WHERE literalment → verificar. I la comparació entre el recompte previ i l'UPDATE N com a alarma.
  • BEGIN … verificar … COMMIT / ROLLBACK com a xarxa de seguretat real, amb els seus tres advertiments pràctics: bloqueja files, un error avorta el bloc sencer, i les seqüències no es desfan. El model complet —ACID, aïllament, bloqueigs— és el mòdul 9.
  • Diverses columnes en un sol SET, que evita l'estat intermedi visible de dos UPDATE seguits.
  • Càlculs sobre el valor anterior (SET preu = preu * 1.05) i la regla d'or: totes les assignacions s'avaluen simultàniament sobre la fila antiga. Per això l'ordre és igual, per això SET a = b, b = a intercanvia dues columnes sense variable temporal, i per això no es poden encadenar càlculs.
  • UPDATE ... FROM per actualitzar a partir d'una altra taula, amb la seva trampa mortal: un aparellament 1:N no suma, tria una coincidència arbitrària i no avisa. I les quatre sintaxis incompatibles de PostgreSQL, MySQL, SQL Server i Oracle.
  • RETURNING per veure el resultat, que no pot retornar el valor anterior (llevat de l'OUTPUT de SQL Server).
  • Els cinc errors de restricció, idèntics als d'INSERT, amb la fila original intacta quan fallen.
  • Actualitzar a NULL i la seva semàntica, i ON UPDATE CASCADE, que només cal amb claus naturals mutables.
  • La idempotència: SET col = valor_fix sí, SET col = col * k no; i el patró que ho resol, acotar el WHERE a les files "encara no processades" perquè la segona execució retorni UPDATE 0.
  • I els casos reals: pujada de tarifes per categoria, tancament de comandes antigues, correcció d'un email i descatalogació amb actiu = FALSE, l'esborrat lògic.

Aquest últim cas és la porta de la lliçó següent. A Instrucció DELETE aprendràs a eliminar files de debò —amb el mateix protocol, reforçat—, veuràs la diferència entre DELETE i TRUNCATE, comprovaràs amb recomptes què passa en esborrar una fila referenciada segons que el seu ON DELETE sigui RESTRICT, CASCADE o SET NULL, i arribaràs a la pregunta de fons: si actiu = FALSE conserva l'històric i DELETE el destrueix, per què esborrar res mai? La resposta té a veure amb l'auditoria, amb el rendiment i amb el dret de supressió de les dades personals.

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