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çarbotigaverda.sql.- En un sistema real, un
UPDATEsobre dades vives ha d'anar revisat per una altra persona i executat dins d'una transacció.
Contingut
- La sintaxi, i el
WHEREcom a xarxa de seguretat - Què passa exactament si oblides el
WHERE - El protocol dels cinc passos
BEGIN…ROLLBACK/COMMIT: la xarxa de debò- Actualitzar diverses columnes alhora
- Calcular sobre el valor anterior: pujar preus un 5 %
- Per què l'ordre de les assignacions no importa
UPDATE ... FROM: actualitzar a partir d'una altra taulaRETURNING: veure què has canviatUPDATEque viola una restricció- Actualitzar a
NULL, i l'efecte d'ON UPDATE CASCADE - Idempotència: per què importa en reexecutar un script
- Quatre casos de negoci de BotigaVerda
- Errors habituals i consells
- Exercicis
- Conclusió
- La sintaxi, i el
WHERE com a xarxa de seguretat
WHERE com a xarxa de seguretatTres 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 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:
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.
- Què passa exactament si oblides el
WHERE
WHERENo hi ha misteri: l'UPDATE s'aplica a totes les files de la taula.
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.
| 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
UPDATEo unDELETEsenseWHERE, ipsqlté fins i tot una opció per a això (\set ON_ERROR_STOP onno fa això, però eines com pgcli o DBeaver sí que ho fan). Però l'estàndard SQL no ho contempla: unUPDATEsenseWHEREé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.
- 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.
| 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
| 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.
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
| 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'UPDATEi tot i així els preus han quedat amb dos decimals. El motiu és quepreuésNUMERIC(10,2): en assignar3.25 * 1.05 = 3.4125a 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 elROUNDexplícit perquè el lector del teu codi sàpiga que la decisió va ser teva i no del sistema de tipus.
BEGIN … ROLLBACK / COMMIT: la xarxa de debò
BEGIN … ROLLBACK / 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:
Ara, abans de confirmar, verifica:
| 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é:
I si està malament —o si aquell UPDATE 4 hagués estat un UPDATE 20—:
| 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.
É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.
- Actualitzar diverses columnes alhora
Se separen per comes dins del mateix SET:
| 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í.
- 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 gratisLa 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.
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.
- 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.
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:
| 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;| 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:
- Pots escriure les assignacions en qualsevol ordre.
SET a = ..., b = ...iSET b = ..., a = ...són equivalents. - No pots encadenar càlculs dins del mateix
UPDATE.SET preu = preu * 1.1, iva = preu * 0.21calcularia l'IVA sobre el preu vell, no sobre el que acaba de pujar. Si necessites encadenar, calen dosUPDATE(dins de la mateixa transacció). - No pots assignar dues vegades la mateixa columna. PostgreSQL ho rebutja:
UPDATE ... FROM: actualitzar a partir d'una altra taula
UPDATE ... FROM: actualitzar a partir d'una altra taulaFins 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:
| 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;| 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 deproductesaparella amb diverses files delinies_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, aquestUPDATEen descomptaria només una.És exactament la multiplicació de files del mòdul 3, però molt més perillosa: en un
SELECTla 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 alFROM— mòdul 7. Mentrestant: abans d'unUPDATE ... 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;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 |
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ó sí 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.
RETURNING: veure què has canviat
RETURNING: veure què has canviatIgual 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 |
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.columnaretorna 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.
UPDATE que viola una restricció
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:
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:
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:
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:
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.
- Actualitzar a
NULL, i l'efecte d'ON UPDATE CASCADE
NULL, i l'efecte d'ON UPDATE CASCADEPosar 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 |
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);Ara l'empresa decideix migrar a codis ISO de dues lletres:
| 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.
- 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 |
Sí | Fixa un estat absolut |
SET col = altra_columna |
Sí | 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:
- Fer-lo idempotent amb el
WHERE. Si pots identificar les files "encara no processades", la segona passada no troba res:
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.
-
Registrar que ja s'ha fet, en una taula de control o amb una columna de marca, i comprovar-ho abans.
-
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.
- 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 |
| estat | comandes |
|---|---|
| lliurat | 16 |
| pagat | 2 |
| cancellat | 1 |
| pendent | 1 |
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:
| id | nom | cognoms | 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 | |
|---|---|---|---|
| 8 | Tiago | Almeida Nunes | [email protected] |
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 alWHEREno tocaries cap fila (UPDATE 0) i podries creure que ja estava bé. La PK és sempre el filtre més segur. - El
UNIQUEcontinua vigilant. Si l'adreça nova ja pertanyés a un altre client, l'UPDATEfallaria ambduplicate 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:
| id | nom | preu | stock | actiu |
|---|---|---|---|---|
| 13 | Espelmes de cera de soja (pack 2) | 13.75 | 0 | false |
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
WHEREde memòria en passar delSELECTa 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. UnBEGINcosta sis lletres. - Deixar una transacció oberta. Bloqueja files per a la resta de sessions. Confirma o desfés abans d'aixecar-te.
- Esperar que
RETURNINGretorni 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.21fa servir l'avell per calcularb. Si necessites encadenar, dosUPDATE. - Fer servir
UPDATE ... FROMamb un aparellament 1:N. No suma: tria una coincidència arbitrària i no avisa. Comprova-ho amb unGROUP BY ... HAVING COUNT(*) > 1abans. - Repetir la taula destinació al
FROMde 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 * 2ambcostnul dónaNULLi peta contra elNOT NULL. - Escriure
UPDATEno idempotents en scripts automàtics.preu = preu * 1.05executat dues vegades puja un 10,25 %. Fixa valors absoluts o acota amb elWHERE. - 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
WHEREmés segur que existeix. - Consell: previsualitza el
SETcom 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 taulaprimer. Trenta segons que valen per una nit sencera.
Exercicis
Treballa sobre la base acabada de recarregar i fes servir BEGIN … ROLLBACK 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:
- Pujar el preu un 8 %, arrodonint a dos decimals.
- Pujar el cost un 3 %, arrodonint a dos decimals.
- 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';- Troba l'error i explica què fa realment aquesta sentència.
- Reescriu-la correctament, amb àlies, i digues quantes files hauria de tocar sobre la base acabada de recarregar.
- Explica per què aquesta operació seria catastròfica si el
WHEREincloguéscomandes.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);- Escriu l'
UPDATE ... FROMque suma les unitats rebudes a l'estoc de cada producte. - Comprova abans que l'aparellament és 1:1.
- Mostra l'estat abans i després, i explica què li passa al producte 13.
- É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 |
-- 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 |
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;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;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 |
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:
- Marcar les recepcions ja processades. Afegir a
recepcio_magatzemuna columnaprocessada BOOLEAN NOT NULL DEFAULT FALSE, filtrar perWHERE NOT r.processadaa l'UPDATEi marcar-la aTRUEa la mateixa transacció. La segona execució no troba res. - Registrar cada moviment en una taula
moviments_stockamb 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. - 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 elWHEREfa la mateixa feina que en unSELECTperò amb conseqüències diferents.UPDATE Ncompta 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:
SELECTamb elWHEREdefinitiu → comptar files → previsualitzar els valors nous com a columna calculada → convertir-lo enUPDATEcopiant elWHEREliteralment → verificar. I la comparació entre el recompte previ i l'UPDATE Ncom a alarma. BEGIN… verificar …COMMIT/ROLLBACKcom 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 dosUPDATEseguits. - 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 = aintercanvia dues columnes sense variable temporal, i per això no es poden encadenar càlculs. UPDATE ... FROMper 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.RETURNINGper veure el resultat, que no pot retornar el valor anterior (llevat de l'OUTPUTde SQL Server).- Els cinc errors de restricció, idèntics als d'
INSERT, amb la fila original intacta quan fallen. - Actualitzar a
NULLi la seva semàntica, iON UPDATE CASCADE, que només cal amb claus naturals mutables. - La idempotència:
SET col = valor_fixsí,SET col = col * kno; i el patró que ho resol, acotar elWHEREa les files "encara no processades" perquè la segona execució retorniUPDATE 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
- 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
