DELETE elimina files. És l'operació més senzilla del mòdul i la que exigeix més criteri, perquè planteja una pregunta que INSERT i UPDATE no plantegen: de debò cal esborrar-ho?

BotigaVerda ja te'n va donar la resposta sense dir-t'ho. Les seves taules productes i proveidors tenen una columna actiu. Aquell booleà existeix perquè, en un sistema real, un producte que s'ha venut no s'esborra mai: es retira del catàleg. Esborrar-lo destruiria l'històric de facturació, i de fet la clau forana linies_comanda.producte_id → productes.id està declarada ON DELETE RESTRICT precisament per impedir-ho. Tota aquella discussió —esborrat lògic davant d'esborrat físic— té aquí la seva lliçó.

Abans arribarem al concret: el protocol de seguretat reforçat, la diferència entre DELETE i TRUNCATE, i què passa exactament en esborrar una fila de la qual en depenen d'altres, demostrat amb recomptes abans i després per a les tres accions ON DELETE que BotigaVerda declara.

⚠️ Avís de seguretat

DELETE és una operació destructiva i irreversible un cop confirmada. A diferència d'UPDATE, que substitueix un valor per un altre, DELETE fa desaparèixer la fila sencera — i, amb ON DELETE CASCADE, files d'altres taules que no has esmentat.

  • Executa tots els exemples sobre la teva base de dades de pràctiques (botigaverda), mai en producció.
  • Fes una còpia de seguretat prèvia: pg_dump -U curs_sql -d botigaverda -f copia.sql. Per tornar a l'estat inicial n'hi ha prou amb rellançar botigaverda.sql.
  • Treballa sempre dins de BEGINROLLBACK / COMMIT mentre experimentes.
  • En un sistema real, un esborrat sobre dades vives ha d'anar revisat per una altra persona, i les polítiques d'esborrat de dades personals requereixen revisió legal (apartat 9).

Contingut

  1. La sintaxi, i el protocol reforçat
  2. DELETE sense WHERE davant de TRUNCATE
  3. Esborrar una fila referenciada: RESTRICT
  4. Esborrar una fila referenciada: CASCADE
  5. Esborrar una fila referenciada: SET NULL
  6. El perill del CASCADE i l'alternativa professional
  7. DELETE ... USING: esborrar segons una altra taula
  8. RETURNING: conservar el que esborres
  9. Esborrat lògic davant d'esborrat físic
  10. Dades personals i dret de supressió
  11. Recuperació després d'un esborrat accidental
  12. Errors habituals i consells
  13. Exercicis
  14. Conclusió

  1. La sintaxi, i el protocol reforçat

DELETE FROM taula
WHERE  condició;

Només dues peces: quina taula i quines files. No hi ha SET, no hi ha valors. Tota la responsabilitat recau sobre el WHERE.

DELETE FROM productes WHERE id = 19;
DELETE 1

Igual que UPDATE N, el número et diu quantes files ha eliminat. I aquí importa encara més, perquè no hi ha cap valor nou que puguis inspeccionar després: si el número no és el que esperaves, ja és tard.

El protocol dels cinc passos de 05-03 s'aplica igual, amb dos reforços:

Pas A UPDATE A DELETE
1 SELECT amb el WHERE definitiu Igual, però amb SELECT *: vols veure la fila sencera que desapareixerà
2 Comptar files Igual
3 Previsualitzar els valors nous Substituït per: comprovar quines files dependents s'endurà per davant
4 UPDATE copiant el WHERE DELETE copiant el WHERE, sempre dins de BEGIN
5 Verificar Verificar recomptes de totes les taules afectades, no només de la que esborres

El pas 3 és el nou, i és el que distingeix un DELETE segur d'una catàstrofe. Abans d'esborrar una comanda, mira quantes línies i devolucions té:

SELECT (SELECT COUNT(*) FROM linies_comanda WHERE comanda_id = 6) AS linies,
       (SELECT COUNT(*) FROM devolucions    WHERE comanda_id = 6) AS devolucions;
linies devolucions
2 1

Ara ja saps que aquell DELETE 1 eliminarà quatre files en tres taules. L'apartat 4 ho demostra.

I el flux complet:

flowchart TD
    A["SELECT * amb el WHERE<br/>veure les files senceres"] --> B["Comptar files dependents<br/>a les taules filles"]
    B --> C{"Els números<br/>són els esperats?"}
    C -->|No| A
    C -->|Sí| D["BEGIN"]
    D --> E["DELETE"]
    E --> F["Recompte a TOTES<br/>les taules afectades"]
    F --> G{"Coincideix?"}
    G -->|No| H["ROLLBACK"]
    G -->|Sí| I["COMMIT"]
    H --> A

  1. DELETE sense WHERE davant de TRUNCATE

Sense WHERE, DELETE buida la taula:

-- ⚠️ INCORRECTA fora d'una transacció
DELETE FROM ressenyes;
DELETE 12

Les dotze ressenyes, fora. Exactament el mateix problema que l'UPDATE sense WHERE, amb la mateixa absència d'avís.

Per buidar una taula hi ha una instrucció específica, TRUNCATE:

TRUNCATE TABLE ressenyes;
TRUNCATE TABLE

Fan el mateix i no s'assemblen en res:

DELETE FROM taula TRUNCATE TABLE taula
Subllenguatge DML DDL
Admet WHERE No: és tot o res
Velocitat Proporcional al nombre de files Gairebé instantània, independent del volum
Com ho fa Marca cada fila com a esborrada, una a una Descarta els fitxers de dades sencers
Registre d'escriptura (WAL) Una entrada per fila Mínim
Espai en disc No s'allibera fins a un VACUUM S'allibera immediatament
Transaccional a PostgreSQL (es pot fer ROLLBACK)
Transaccional a MySQL No: confirma implícitament
Dispara TRIGGER de fila (BEFORE/AFTER DELETE) No (només triggers de sentència)
Reinicia la identitat No Opcional: TRUNCATE ... RESTART IDENTITY
Retorna el nombre de files (DELETE 12) No
RETURNING No
Permís necessari DELETE TRUNCATE (més restrictiu)
Respecta les FK Sí, aplica les accions ON DELETE Falla si hi ha FK apuntant a la taula, llevat de CASCADE

Aquella última fila mereix una demostració:

TRUNCATE TABLE comandes;
ERROR:  cannot truncate a table referenced in a foreign key constraint
DETAIL:  Table "linies_comanda" references "comandes".
HINT:  Truncate table "linies_comanda" at the same time, or use TRUNCATE ... CASCADE.

TRUNCATE no executa les accions ON DELETE: o buides tot l'arbre alhora, o no en buides res.

TRUNCATE TABLE comandes CASCADE;
NOTICE:  truncate cascades to table "linies_comanda"
NOTICE:  truncate cascades to table "devolucions"
TRUNCATE TABLE

I si a més vols que les seqüències tornin a començar a 1:

TRUNCATE TABLE comandes, linies_comanda, devolucions RESTART IDENTITY;
TRUNCATE TABLE

Quan fer servir cadascun. TRUNCATE per buidar taules de treball, d'staging o de proves, on vols començar de zero i la velocitat importa. DELETE per a tota la resta, i sempre que hi hagi un WHERE pel mig. Un DELETE FROM taula; sense condició sobre una taula gran és el pitjor dels dos móns: lent, amb el WAL disparat i sense alliberar espai.

  1. Esborrar una fila referenciada: RESTRICT

Aquí comença l'interessant. Quan la fila que esborres és el "pare" d'una clau forana, el motor aplica l'acció declarada a l'ON DELETE. BotigaVerda declara les tres.

RESTRICT és la barrera: prohibeix esborrar. La porta linies_comanda.producte_id, entre d'altres.

El producte 1 (Oli d'oliva) apareix en 5 línies de comanda i té 2 ressenyes:

SELECT (SELECT COUNT(*) FROM linies_comanda WHERE producte_id = 1) AS linies,
       (SELECT COUNT(*) FROM ressenyes      WHERE producte_id = 1) AS ressenyes;
linies ressenyes
5 2
DELETE FROM productes WHERE id = 1;
ERROR:  update or delete on table "productes" violates foreign key constraint "linies_comanda_producte_id_fkey" on table "linies_comanda"
DETAIL:  Key (id)=(1) is still referenced from table "linies_comanda".

Llegeix-lo amb calma, perquè és l'error més freqüent del mòdul:

Fragment Què significa
update or delete on table "productes" La taula que intentaves tocar
violates foreign key constraint "linies_comanda_producte_id_fkey" Quina FK ho impedeix
on table "linies_comanda" On és aquella FK: a la taula filla
Key (id)=(1) is still referenced El valor que continua en ús

Compara'l amb l'error d'INSERT de 05-02: allà deia is not present in table (referenciaves una cosa que no existeix); aquí diu is still referenced from table (una cosa depèn del que vols esborrar). Les dues meitats de la integritat referencial.

Ara un producte que que es pot esborrar. El 19 (Desodorant natural) és un dels tres que no s'han venut mai, i no té ressenyes:

BEGIN;

SELECT (SELECT COUNT(*) FROM linies_comanda WHERE producte_id = 19) AS linies,
       (SELECT COUNT(*) FROM ressenyes      WHERE producte_id = 19) AS ressenyes;
linies ressenyes
0 0
DELETE FROM productes WHERE id = 19
RETURNING id, nom, categoria_id, proveidor_id, preu, stock;
id nom categoria_id proveidor_id preu stock
19 Desodorant natural en barra 50 g 5 4 7.80 75
DELETE 1
SELECT COUNT(*) AS productes FROM productes;
productes
19
ROLLBACK;

Del catàleg de 20 en queden 19. La restricció RESTRICT no és un obstacle: és una funció. T'està dient "aquest producte té història, no el destrueixis". I el producte 19 no en té.

  1. Esborrar una fila referenciada: CASCADE

CASCADE propaga l'esborrat a les files filles. A BotigaVerda el porten quatre claus foranes: linies_comanda.comanda_id, devolucions.comanda_id, ressenyes.producte_id i ressenyes.client_id.

La comanda 6 és el cas perfecte: està cancel·lada, té 2 línies i 1 devolució.

BEGIN;

-- Recompte ABANS
SELECT (SELECT COUNT(*) FROM comandes)       AS comandes,
       (SELECT COUNT(*) FROM linies_comanda) AS linies,
       (SELECT COUNT(*) FROM devolucions)    AS devolucions;
comandes linies devolucions
20 47 3

I el que penja de la comanda 6:

SELECT lc.id, lc.producte_id, p.nom, lc.quantitat, lc.preu_unitari
FROM   linies_comanda AS lc
JOIN   productes      AS p ON lc.producte_id = p.id
WHERE  lc.comanda_id = 6
ORDER BY lc.id;
id producte_id nom quantitat preu_unitari
14 1 Oli d'oliva verge extra 500 ml 1 12.50
15 8 Oli corporal d'ametlles 200 ml 1 14.25
SELECT id, comanda_id, motiu, data, import FROM devolucions WHERE comanda_id = 6;
id comanda_id motiu data import
1 6 Comanda cancel·lada pel client abans de l'enviament 2025-05-25 26.75

Ara l'esborrat:

DELETE FROM comandes WHERE id = 6;
DELETE 1

DELETE 1. Una sola fila, diu PostgreSQL. Mirem els recomptes:

SELECT (SELECT COUNT(*) FROM comandes)       AS comandes,
       (SELECT COUNT(*) FROM linies_comanda) AS linies,
       (SELECT COUNT(*) FROM devolucions)    AS devolucions;
comandes linies devolucions
19 45 2

Han desaparegut quatre files en tres taules, i el comptador només n'ha informat d'una. Aquí està, en tota la seva cruesa, el perill del CASCADE: DELETE N compta les files que tu has esborrat, no les que el motor ha esborrat en cadena.

flowchart TD
    A["DELETE FROM comandes<br/>WHERE id = 6"] --> B["comandes: 20 → 19<br/>DELETE 1"]
    B --> C["linies_comanda.comanda_id<br/>ON DELETE CASCADE"]
    B --> D["devolucions.comanda_id<br/>ON DELETE CASCADE"]
    C --> E["línies 14 i 15<br/>47 → 45"]
    D --> F["devolució 1<br/>3 → 2"]
    E --> G["Total real:<br/>4 files en 3 taules"]
    F --> G
ROLLBACK;

I les cascades es poden encadenar. Si linies_comanda tingués al seu torn una taula filla amb CASCADE, l'esborrat continuaria baixant. En un esquema gran, un sol DELETE es pot propagar per mitja base de dades sense que res t'ho indiqui.

Comprovar l'abast abans d'esborrar

La manera de no endur-te sorpreses és preguntar al catàleg del sistema quines claus foranes apunten a una taula:

SELECT tc.table_name   AS taula_filla,
       kcu.column_name AS columna,
       rc.delete_rule  AS accio_on_delete
FROM   information_schema.table_constraints    AS tc
JOIN   information_schema.key_column_usage     AS kcu
       ON tc.constraint_name = kcu.constraint_name
JOIN   information_schema.referential_constraints AS rc
       ON tc.constraint_name = rc.constraint_name
JOIN   information_schema.constraint_column_usage AS ccu
       ON rc.unique_constraint_name = ccu.constraint_name
WHERE  tc.constraint_type = 'FOREIGN KEY'
  AND  ccu.table_name = 'comandes'
ORDER BY taula_filla;
taula_filla columna accio_on_delete
devolucions comanda_id CASCADE
linies_comanda comanda_id CASCADE

Més ràpid encara, dins de psql, la secció Referenced by de \d comandes:

Referenced by:
    TABLE "devolucions" CONSTRAINT "devolucions_comanda_id_fkey" FOREIGN KEY (comanda_id) REFERENCES comandes(id) ON DELETE CASCADE
    TABLE "linies_comanda" CONSTRAINT "linies_comanda_comanda_id_fkey" FOREIGN KEY (comanda_id) REFERENCES comandes(id) ON DELETE CASCADE

Consell: fes \d taula abans de qualsevol DELETE sobre una taula que no coneguis al dit. La secció Referenced by et diu en cinc segons si desencadenaràs una cascada.

  1. Esborrar una fila referenciada: SET NULL

SET NULL no esborra el fill: li treu la referència. BotigaVerda ho declara a comandes.empleat_id, clients.referit_per_id i empleats.cap_id.

El comercial Óscar Peris Blasco (empleat 4) deixa l'empresa. Té 4 comandes assignades:

BEGIN;

SELECT id, client_id, empleat_id, data_comanda, estat
FROM   comandes WHERE empleat_id = 4 ORDER BY id;
id client_id empleat_id data_comanda estat
2 2 4 2025-03-12 lliurat
6 5 4 2025-05-23 cancellat
10 9 4 2025-08-03 lliurat
16 4 4 2025-12-19 enviat
SELECT COUNT(*) AS comandes_sense_empleat FROM comandes WHERE empleat_id IS NULL;
comandes_sense_empleat
10
DELETE FROM empleats WHERE id = 4
RETURNING id, nom, cognoms, carrec, cap_id, salari;
id nom cognoms carrec cap_id salari
4 Óscar Peris Blasco Comercial 2 28500.00
DELETE 1
SELECT (SELECT COUNT(*) FROM empleats)                            AS empleats,
       (SELECT COUNT(*) FROM comandes)                            AS comandes,
       (SELECT COUNT(*) FROM comandes WHERE empleat_id IS NULL)   AS sense_empleat;
empleats comandes sense_empleat
7 20 14

Les 20 comandes continuen allà. El que ha canviat és que les quatre de l'Óscar ara tenen empleat_id a NULL: de 10 comandes sense comercial hem passat a 14.

I aquí hi ha una lliçó de 04-03 que convé subratllar. Abans, empleat_id IS NULL significava inequívocament "comanda entrada pel web". Ara significa dues coses diferents: "comanda web" o "el comercial que la va gestionar ja no és a l'empresa". El NULL ha perdut precisió semàntica sense que ningú no ho hagi decidit. És un efecte secundari clàssic de SET NULL, i la raó que molts equips prefereixin conservar l'empleat amb una marca de baixa en lloc d'esborrar-lo.

ROLLBACK;

SET NULL en una relació reflexiva

Un cas més vistós: esborrar l'Andrés Company Talens (empleat 2), Responsable de vendes, que no gestiona cap comanda però té tres subordinats.

BEGIN;

SELECT id, nom, cognoms, carrec, cap_id FROM empleats WHERE cap_id = 2 ORDER BY id;
id nom cognoms carrec cap_id
4 Óscar Peris Blasco Comercial 2
5 Laia Puig Sanchis Comercial 2
6 Marc Estévez Roig Atenció al client 2
DELETE FROM empleats WHERE id = 2;
DELETE 1
SELECT id, nom || ' ' || cognoms AS empleat, carrec, cap_id
FROM   empleats ORDER BY id;
id empleat carrec cap_id
1 Rosa Alcázar Vives Directora general (null)
3 Beatriz Nadal Ripoll Responsable de logística 1
4 Óscar Peris Blasco Comercial (null)
5 Laia Puig Sanchis Comercial (null)
6 Marc Estévez Roig Atenció al client (null)
7 Irene Salvador Mira Operària de magatzem 3
8 Daniel Vercher Lluch Analista de dades 1

Tres empleats s'han quedat sense cap, i l'organigrama que 01-06 dibuixava amb tanta pulcritud té ara quatre arrels en lloc d'una. L'empresa no s'ha reorganitzat: simplement ha desaparegut un node intermedi de l'arbre i SET NULL ha fet l'única cosa que sap fer.

ROLLBACK;

Les tres accions, en una taula

Acció Què li passa al fill Quan triar-la A BotigaVerda
RESTRICT / NO ACTION Res: s'impedeix l'esborrat El fill no pot quedar-se sense pare i el pare no hauria de desaparèixer comandes.client_id, linies_comanda.producte_id, productes.categoria_id, productes.proveidor_id
CASCADE S'esborra també El fill és part del pare (composició): no existeix sense ell linies_comanda.comanda_id, devolucions.comanda_id, ressenyes.producte_id, ressenyes.client_id
SET NULL Perd la referència, sobreviu La relació és opcional i el fill té sentit propi comandes.empleat_id, clients.referit_per_id, empleats.cap_id

  1. El perill del CASCADE i l'alternativa professional

CASCADE és còmode. Un DELETE i tot l'arbre desapareix netament, sense files òrfenes. I precisament per això és perillós:

Risc Detall
Abast invisible DELETE 1 pot significar quatre files, o quatre-centes mil. El comptador no ho diu
Es propaga en cadena Si el fill té fills amb CASCADE, l'esborrat continua baixant sense límit
Està declarat lluny La cascada viu al DDL, escrit fa tres anys per una altra persona. Qui executa el DELETE pot no saber que existeix
Rendiment imprevisible Un CASCADE sobre una FK sense índex recorre la taula filla sencera per cada fila pare esborrada (mòdul 8)
Es pot saltar regles de negoci Esborra sense passar per la lògica de l'aplicació: comptadors, agregats i auditories es queden desincronitzats

Per això molts equips adopten una política més conservadora: RESTRICT a totes les FK, i esborrat explícit en l'ordre correcte, dins d'una transacció.

BEGIN;

DELETE FROM devolucions    WHERE comanda_id = 6;   -- 1) els néts
DELETE FROM linies_comanda WHERE comanda_id = 6;   -- 2) els fills
DELETE FROM comandes       WHERE id = 6;           -- 3) el pare

COMMIT;
BEGIN
DELETE 1
DELETE 2
DELETE 1
COMMIT

Compara les dues sortides. Amb CASCADE: DELETE 1, i quatre files fora. Explícitament: DELETE 1, DELETE 2, DELETE 1quatre files, i les veus totes. Escrius tres línies en lloc d'una i guanyes visibilitat completa sobre el que estàs destruint. En un sistema amb dades reals, aquell canvi val molt més del que costa.

CASCADE RESTRICT + esborrat explícit
Línies de codi 1 N (una per nivell)
Visibilitat de l'abast Cap Total
Risc d'oblidar un nivell Cap Existeix (te'l dirà l'error)
Protecció davant d'un DELETE accidental Cap La FK et frena
Adequat per a Composició estricta i ben acotada Gairebé tota la resta

BotigaVerda fa servir CASCADE en quatre llocs i tots quatre són composició real: una línia de comanda, una devolució i una ressenya no signifiquen res sense el seu pare. És l'ús correcte. La regla de 01-05 continua vigent: pregunta't si la fila filla té sentit per si sola, i si dubtes, RESTRICT.

  1. DELETE ... USING: esborrar segons una altra taula

Igual que UPDATEFROM, DELETEUSING: permet decidir què esborrar en funció d'una altra taula.

DELETE FROM taula_desti AS d
USING altra_taula AS a
WHERE d.clau = a.clau
  AND condicions;

Depuració del catàleg: eliminar les línies de les comandes cancel·lades.

BEGIN;

-- Pas 1: veure què s'esborrarà
SELECT lc.id, lc.comanda_id, co.estat, p.nom, lc.quantitat, lc.preu_unitari
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 = 'cancellat'
ORDER BY lc.id;
id comanda_id estat nom quantitat preu_unitari
14 6 cancellat Oli d'oliva verge extra 500 ml 1 12.50
15 6 cancellat Oli corporal d'ametlles 200 ml 1 14.25
-- Pas 2: el DELETE
DELETE FROM linies_comanda AS lc
USING comandes AS co
WHERE lc.comanda_id = co.id
  AND co.estat      = 'cancellat'
RETURNING lc.id, lc.comanda_id, lc.producte_id, lc.quantitat;
id comanda_id producte_id quantitat
14 6 1 1
15 6 8 1
DELETE 2
SELECT COUNT(*) AS linies FROM linies_comanda;
linies
45
ROLLBACK;

Regles d'USING, totes heretades d'UPDATE ... FROM:

  • Pots posar-hi diverses taules, separades per comes o amb JOIN.
  • La taula destinació no es repeteix a l'USING. Si ho fas, producte cartesià silenciós.
  • Al contrari que a UPDATE ... FROM, un aparellament 1:N no és un problema aquí: una fila que aparella amb tres s'esborra una sola vegada. DELETE és idempotent per naturalesa.

Nota de dialecte: USING és de PostgreSQL. MySQL escriu DELETE lc FROM linies_comanda lc JOIN comandes co ON ... WHERE ... (fixa't en l'àlies repetit després de DELETE). SQL Server fa servir DELETE d FROM desti d JOIN altra a ON .... Oracle no té cap de les dues i obliga a una subconsulta correlacionada. La forma portable és sempre una subconsulta: DELETE FROM linies_comanda WHERE comanda_id IN (SELECT id FROM comandes WHERE estat = 'cancellat') — mòdul 7.

  1. RETURNING: conservar el que esborres

A DELETE, RETURNING retorna les files tal com eren just abans de desaparèixer. És l'única manera de veure-les sense haver-les consultat abans.

DELETE FROM devolucions
WHERE  import < 20
RETURNING id, comanda_id, motiu, data, import;
id comanda_id motiu data import
3 13 El format no correspon al que s'esperava 2025-10-30 19.80
DELETE 1

Però veure-les no és conservar-les. Per a això, el patró professional combina INSERT ... SELECT (05-02) amb el DELETE, dins d'una transacció:

BEGIN;

-- 1) Taula d'arxiu (una sola vegada)
CREATE TABLE devolucions_arxiu (
    id          INTEGER       NOT NULL,
    comanda_id  INTEGER       NOT NULL,
    motiu       VARCHAR(200)  NOT NULL,
    data        DATE          NOT NULL,
    import      NUMERIC(10,2) NOT NULL,
    data_baixa  DATE          NOT NULL DEFAULT CURRENT_DATE,
    CONSTRAINT pk_devolucions_arxiu PRIMARY KEY (id)
);

-- 2) Copiar el que s'esborrarà
INSERT INTO devolucions_arxiu (id, comanda_id, motiu, data, import)
SELECT d.id, d.comanda_id, d.motiu, d.data, d.import
FROM   devolucions AS d
WHERE  d.import < 20;

-- 3) Esborrar
DELETE FROM devolucions WHERE import < 20;

COMMIT;
BEGIN
CREATE TABLE
INSERT 0 1
DELETE 1
COMMIT

Dins d'una transacció, o passen les tres coses o no en passa cap: és impossible que esborris sense haver arxivat. Fixa't en un detall del DDL de la taula d'arxiu: no porta claus foranes. Si en portés, no podries arxivar una devolució la comanda de la qual també s'esborrarà. Les taules d'arxiu són deliberadament laxes.

RETURNING completa la trilogia: INSERT ... RETURNING et dóna l'id generat, UPDATE ... RETURNING el valor nou, DELETE ... RETURNING la fila que se'n va. En els tres casos evita un SELECT addicional i la condició de cursa que porta associada.

  1. Esborrat lògic davant d'esborrat físic

I arribem al fons de la qüestió.

  • Esborrat físic: DELETE FROM productes WHERE id = 13. La fila desapareix.
  • Esborrat lògic: UPDATE productes SET actiu = FALSE WHERE id = 13. La fila continua allà, marcada com a no vigent.

productes.actiu i proveidors.actiu són exactament això. No són un caprici del disseny: són la decisió, presa a 01-05 i ara explicada, que a BotigaVerda res que hagi participat en una operació comercial no s'esborra mai.

SELECT id, nom, preu, stock, actiu, data_alta
FROM   productes
WHERE  NOT actiu;
id nom preu stock actiu data_alta
20 Càpsules d'espirulina 120 u 16.40 55 false 2025-06-01
SELECT id, nom, pais, email, actiu FROM proveidors WHERE NOT actiu;
id nom pais email actiu
5 EcoNordic Supplies Alemanya [email protected] false

EcoNordic Supplies està inactiu i conserva els seus quatre productes al catàleg (10, 13, 18 i 20). Amb un esborrat físic, o bé la FK RESTRICT hauria impedit esborrar-lo, o bé hauries hagut d'esborrar quatre productes que encara es venen.

La comparació completa

Aspecte Esborrat lògic (actiu = FALSE) Esborrat físic (DELETE)
Històric i auditoria Es conserva: saps que va existir i quan va deixar d'estar vigent Es destrueix
Integritat referencial Intacta: els fills continuen apuntant a alguna cosa real Cal decidir RESTRICT, CASCADE o SET NULL
Reversibilitat Trivial: SET actiu = TRUE Només des d'una còpia de seguretat
Informes històrics Continuen quadrant Es descompensen retroactivament
Complexitat de les consultes Totes necessiten WHERE actiu Cap condició extra
Mida de les taules Creix indefinidament Es manté acotada
Rendiment Índexs més grans; cal filtrar sempre Òptim
Unicitat Es complica: pot un codi repetir-se si l'anterior està inactiu? Trivial
Compliment de l'RGPD Problemàtic: la dada continua allà Compleix el dret de supressió

El cost real: WHERE actiu a tot arreu

Aquest és l'inconvenient que tothom subestima:

-- Facturació per categoria del catàleg VIGENT
SELECT cat.id,
       cat.nom AS categoria,
       COUNT(*)              AS productes,
       ROUND(AVG(p.preu), 2) AS preu_mitja
FROM   productes  AS p
JOIN   categories AS cat ON p.categoria_id = cat.id
WHERE  p.actiu
GROUP BY cat.id, cat.nom
ORDER BY productes DESC, cat.id;
id categoria productes preu_mitja
1 Alimentació 5 6.18
2 Cosmètica natural 4 11.54
3 Llar sostenible 4 10.09
4 Begudes 4 8.90
5 Higiene personal 2 5.65

Cinc categories, no sis. Complements desapareix de l'informe perquè el seu únic producte (el 20) està inactiu. I aquella desaparició depèn enterament que algú es recordés d'escriure WHERE p.actiu. Oblidar-ho una sola vegada en un informe de direcció i estaràs comptant referències que ja no es venen.

Aquell oblit té solució, i és una vista:

-- Avançament del mòdul 10: no ho escriguis encara
CREATE VIEW productes_vigents AS
SELECT * FROM productes WHERE actiu;

A partir d'aquí, les consultes de catàleg van contra productes_vigents i el filtre és impossible d'oblidar. Les vistes són la solució canònica al principal inconvenient de l'esborrat lògic, i s'estudien a la lliçó 10-01.

Quan fer servir cadascun

Fes servir esborrat lògic quan… Fes servir esborrat físic quan…
La dada ha participat en operacions (vendes, factures, contractes) La dada és transitòria o de treball (sessions, memòries cau, staging)
Hi ha obligació legal o comptable de conservar-la És brossa: proves, duplicats, errors de càrrega
Altres taules la referencien Ningú no la referencia i mai no es va referenciar
Pot necessitar reactivar-se El volum és un problema real de rendiment
Vols saber que va existir Hi ha obligació legal de suprimir-la (apartat següent)

I una tercera via, cada cop més habitual: esborrat lògic amb data. En lloc d'un booleà, una columna data_baixa DATE nul·lable. Ocupa el mateix, distingeix "vigent" de "donat de baixa" igual de bé (data_baixa IS NULL) i a més et diu quan va passar, que sol ser just el que preguntarà algú sis mesos després. Si dissenyes de zero, prefereix-la al booleà.

  1. Dades personals i dret de supressió

Hi ha un cas on l'esborrat lògic no n'hi ha prou: les dades personals.

El Reglament General de Protecció de Dades europeu reconeix el dret de supressió (article 17, l'anomenat "dret a l'oblit"): una persona pot exigir que les seves dades personals s'eliminin, i marcar una fila com a inactiva no és eliminar-la. El nom, l'email i la ciutat continuen a la taula, a les còpies de seguretat i als índexs.

A BotigaVerda, la taula clients conté dades personals: nom, cognoms, email i ciutat. I el seu disseny ja anticipa part del problema:

-- Què passa si un client exerceix el dret de supressió
SELECT (SELECT COUNT(*) FROM comandes  WHERE client_id = 13)      AS comandes,
       (SELECT COUNT(*) FROM ressenyes WHERE client_id = 13)      AS ressenyes,
       (SELECT COUNT(*) FROM clients   WHERE referit_per_id = 13) AS referits;
comandes ressenyes referits
0 0 0

Núria Bosch Ferrer (client 13) és un dels tres clients sense comandes. El seu esborrat és net:

BEGIN;

DELETE FROM clients WHERE id = 13
RETURNING id, nom, cognoms, email, ciutat, pais, data_registre;
id nom cognoms email ciutat pais data_registre
13 Núria Bosch Ferrer [email protected] Barcelona Espanya 2025-06-20
DELETE 1
SELECT COUNT(*) AS clients FROM clients;
clients
14
ROLLBACK;

Però amb un client que sí que ha comprat, el conflicte apareix immediatament:

DELETE FROM clients WHERE id = 5;
ERROR:  update or delete on table "clients" violates foreign key constraint "comandes_client_id_fkey"
DETAIL:  Key (id)=(5) is still referenced from table "comandes".

Ana Belmonte Roca té dues comandes (la 6 i la 18), i comandes.client_id és RESTRICT. Aquí xoquen dues obligacions legítimes:

Obligació Què exigeix
Dret de supressió (RGPD art. 17) Eliminar les dades personals de la persona
Obligació comptable i fiscal Conservar les factures emeses durant el termini legal (a Espanya, uns quants anys)

La solució habitual en sistemes reals no és ni esborrar ni no esborrar: és anonimitzar. Es conserva la fila —i amb ella la comanda, la factura i els totals— però se substitueixen les dades identificatives:

-- Patró d'anonimització. Exemple il·lustratiu: NO l'apliquis
-- en un sistema real sense revisió legal.
UPDATE clients
SET    nom     = 'Client',
       cognoms = 'anonimitzat',
       email   = 'anon-' || id || '@invalid.local',
       ciutat  = NULL
WHERE  id = 5;
UPDATE 1

La comanda 6 continua existint, la comptabilitat quadra, i la dada personal ha desaparegut. L'email es construeix amb l'id per no trencar la restricció UNIQUE, i amb un domini invàlid perquè sigui impossible enviar correu a aquella adreça per accident.

Les ressenyes, en canvi, sí que s'esborren: ressenyes.client_id està declarada ON DELETE CASCADE exactament per aquest motiu, com vas raonar a l'exercici 1 de 01-05. Una ressenya és una opinió personal; una factura és un document comptable.

⚖️ Avís important

L'anterior és una explicació tècnica de patrons habituals, no assessorament jurídic. Els terminis de conservació, què es considera dada personal, quin grau d'anonimització és suficient i quines excepcions apliquen depenen de la legislació vigent, del sector i del cas concret.

Abans d'implantar qualsevol política d'esborrat o anonimització de dades personals en un sistema real, consulta-ho amb el responsable de protecció de dades o amb assessoria jurídica. Un DELETE mal plantejat pot incomplir l'RGPD; un de ben intencionat pot incomplir la normativa comptable. La seguretat, els permisos i el control d'accés a aquestes dades s'estudien a la lliçó 11-03.

  1. Recuperació després d'un esborrat accidental

Ha passat. Has confirmat un DELETE que no havies de fer. Quines opcions hi ha?

Situació Què pots fer
Encara no has fet COMMIT ROLLBACK. És la raó de tot l'apartat 4 de 05-03
Has confirmat, hi ha còpia de seguretat Restaurar el pg_dump en una base auxiliar i reinserir només el que falta amb INSERT ... SELECT
Has confirmat, hi ha PITR configurat Recuperació a un punt en el temps: restaures la còpia base i reprodueixes el WAL fins a l'instant anterior al DELETE
Cap de les anteriors Res. Les dades no hi són

PITR (Point-In-Time Recovery) és la tècnica que permet dir "torna'm la base tal com estava a les 17:42:30 d'ahir". Funciona perquè PostgreSQL escriu tots els canvis en un registre seqüencial —el WAL, Write-Ahead Log— abans d'aplicar-los als fitxers de dades. Amb una còpia base i el WAL posterior es pot reconstruir qualsevol instant intermedi. És també el mecanisme que fa possibles la durabilitat i la replicació, i s'estudia amb la resta del model transaccional al mòdul 9.

La conclusió pràctica, que no depèn de cap tecnologia:

No hi ha recuperació sense còpia de seguretat. L'única pregunta que importa no és "què faig si esborro alguna cosa per error?", sinó "quan es va fer l'última còpia i quan es va provar per última vegada a restaurar-la?". Una còpia que mai no s'ha restaurat no és una còpia: és una suposició.

Per al curs, la teva xarxa de seguretat és molt més simple: botigaverda.sql és idempotent, i rellançar-lo et torna a l'estat inicial en dos segons.

Errors habituals i consells

  • Oblidar el WHERE. Buida la taula sencera sense avís. Mateix error emblemàtic que a UPDATE, amb conseqüències pitjors.
  • Refiar-se del DELETE N amb CASCADE. Compta les files que esborres tu, no les que esborra el motor en cadena. DELETE 1 pot ser quatre files, o quatre milions.
  • No comprovar què depèn de la fila abans d'esborrar-la. \d taula i la seva secció Referenced by en cinc segons.
  • Fer servir TRUNCATE creient que és un DELETE ràpid. No admet WHERE, no dispara triggers de fila, no retorna recompte, exigeix un altre permís i falla si hi ha FK apuntant a la taula.
  • Comptar que TRUNCATE sigui transaccional. A PostgreSQL sí; a MySQL no, i allà no hi ha ROLLBACK possible.
  • Repetir la taula destinació a l'USING. Producte cartesià silenciós, igual que a UPDATE ... FROM.
  • Esborrar registres amb històric comercial. Per a això hi ha actiu = FALSE. La FK RESTRICT intentarà frenar-te; no l'esquivis amb CASCADE.
  • Implantar esborrat lògic i oblidar el WHERE actiu. L'informe continuarà funcionant i comptarà productes descatalogats. Fes servir una vista (mòdul 10).
  • Creure que actiu = FALSE compleix el dret de supressió. No el compleix: la dada personal continua allà. Anonimitzar o esborrar, segons el cas i amb revisió legal.
  • Confondre "no té fills avui" amb "es pot esborrar". El producte 19 es pot esborrar avui; demà, tan bon punt algú el compri, ja no.
  • Consell: SELECT * abans de qualsevol DELETE. Vols veure la fila completa que desapareixerà, no només el seu id.
  • Consell: RETURNING * sempre en un DELETE. No costa res i et deixa constància del que has esborrat, encara que sigui a l'historial de la consola.
  • Consell: prefereix RESTRICT + esborrat explícit a CASCADE. Tres línies en lloc d'una, a canvi de veure exactament què destrueixes.
  • Consell: en dissenyar, fes servir data_baixa DATE en lloc d'actiu BOOLEAN. Costa el mateix i a més et diu quan.

Exercicis

Treballa sobre la base acabada de recarregar i fes servir BEGINROLLBACK a tots els exercicis.

Exercici 1

Per a cadascun d'aquests cinc esborrats, prediu el resultat abans d'executar-lo: si funciona o falla, quantes files s'eliminen en total i en quines taules, i quina acció ON DELETE hi intervé. Després comprova-ho amb recomptes abans i després.

-- a)
DELETE FROM proveidors WHERE id = 5;

-- b)
DELETE FROM comandes WHERE id = 10;

-- c)
DELETE FROM categories WHERE id = 6;

-- d)
DELETE FROM clients WHERE id = 14;

-- e)
DELETE FROM empleats WHERE id = 1;

Exercici 2

Direcció demana retirar del catàleg tots els productes del proveïdor inactiu (EcoNordic Supplies, id 5).

  1. Comprova quins són i quins d'ells s'han venut alguna vegada.
  2. Intenta l'esborrat físic de tots i explica què passa.
  3. Proposa i aplica la solució correcta, justificant per què és la correcta.
  4. Escriu la consulta de catàleg vigent que hauria de fer servir el web a partir d'ara, agrupada per proveïdor.

Exercici 3

Un company vol netejar les ressenyes de productes descatalogats i t'ensenya aquesta sentència:

-- ⚠️ INCORRECTA
DELETE FROM ressenyes
USING productes, ressenyes
WHERE ressenyes.producte_id = productes.id
  AND productes.actiu = FALSE;
  1. Troba l'error i explica què faria realment.
  2. Escriu-la correctament i digues quantes files esborraria sobre la base acabada de recarregar.
  3. Reescriu-la de manera portable, sense USING, indicant quin mòdul cobreix aquella tècnica.
  4. Discuteix si aquesta neteja és bona idea: què es perd i què es guanya?

Solucions

Solució 1

a) Falla. productes.proveidor_id és ON DELETE RESTRICT i EcoNordic té quatre productes:

ERROR:  update or delete on table "proveidors" violates foreign key constraint "productes_proveidor_id_fkey" on table "productes"
DETAIL:  Key (id)=(5) is still referenced from table "productes".

Que el proveïdor estigui actiu = FALSE no canvia res: l'esborrat lògic i la integritat referencial són mecanismes independents.

b) Funciona, i esborra 4 files en 3 taules. La comanda 10 té 2 línies (ids 24 i 25) i 1 devolució (la 2, de 34,02 €), totes dues amb CASCADE:

SELECT (SELECT COUNT(*) FROM comandes)       AS comandes,
       (SELECT COUNT(*) FROM linies_comanda) AS linies,
       (SELECT COUNT(*) FROM devolucions)    AS devolucions;
comandes linies devolucions
20 47 3
DELETE FROM comandes WHERE id = 10;
DELETE 1
comandes linies devolucions
19 45 2

I una cosa que no apareix en cap recompte: la facturació total de BotigaVerda acaba de baixar de 727,95 € a 679,68 €, perquè la comanda 10 aportava 48,27 €. Els ports baixen de 118,25 € a 105,75 €. Cap missatge t'ho ha dit.

c) Falla, i és l'exercici trampa. La categoria 6 (Complements) sembla buida perquè el seu únic producte, el 20, està descatalogat. Però productes.categoria_id és RESTRICT i aquella fila continua existint:

ERROR:  update or delete on table "categories" violates foreign key constraint "productes_categoria_id_fkey" on table "productes"
DETAIL:  Key (id)=(6) is still referenced from table "productes".

El producte 20 està actiu = FALSE, però continua sent una fila de productes i la clau forana no distingeix entre actiu i inactiu. L'esborrat lògic no allibera les restriccions referencials.

d) Funciona, 1 fila. Hugo Iglesias Pardo (client 14) és un dels tres sense comandes, no té ressenyes i no ha referit ningú:

DELETE 1

clients: 15 → 14. Cap cascada, cap SET NULL. És l'únic dels cinc esborrats veritablement innocu.

e) Funciona, i desmunta l'organigrama. Rosa Alcázar Vives (empleada 1) no gestiona comandes, però és cap de tres persones (2, 3 i 8), i empleats.cap_id és SET NULL:

DELETE 1
SELECT COUNT(*) AS sense_cap FROM empleats WHERE cap_id IS NULL;
sense_cap
3

D'1 empleat sense cap passem a 3. La jerarquia s'ha partit en tres subarbres i l'empresa s'ha quedat sense direcció general al model de dades. SET NULL no protesta: fa el que se li va dir.

Resum:

Resultat Files eliminades Acció implicada
a Falla 0 RESTRICT (productes.proveidor_id)
b Funciona 4 en 3 taules CASCADE (linies_comanda, devolucions)
c Falla 0 RESTRICT (productes.categoria_id)
d Funciona 1 Cap: sense dependents
e Funciona 1, més 3 modificades SET NULL (empleats.cap_id)

Solució 2

-- 1) Quins productes i quins s'han venut
SELECT p.id,
       p.nom,
       p.preu,
       p.stock,
       p.actiu,
       COUNT(lc.id) AS vegades_venut
FROM   productes AS p
LEFT JOIN linies_comanda AS lc ON lc.producte_id = p.id
WHERE  p.proveidor_id = 5
GROUP BY p.id, p.nom, p.preu, p.stock, p.actiu
ORDER BY p.id;
id nom preu stock actiu vegades_venut
10 Detergent ecològic concentrat 1 L 11.20 70 true 2
13 Espelmes de cera de soja (pack 2) 13.75 0 true 0
18 Raspall de dents de bambú 3.50 240 true 3
20 Càpsules d'espirulina 120 u 16.40 55 false 0

Dos dels quatre s'han venut (el 10 i el 18). El LEFT JOIN amb COUNT(lc.id) és el patró exacte de 04-05: si haguéssim fet servir INNER JOIN haurien desaparegut justament els dos que ens interessen.

-- 2) Intent d'esborrat físic
DELETE FROM productes WHERE proveidor_id = 5;
ERROR:  update or delete on table "productes" violates foreign key constraint "linies_comanda_producte_id_fkey" on table "linies_comanda"
DETAIL:  Key (id)=(10) is still referenced from table "linies_comanda".

Falla, i falla sencera. No s'esborren els dos que sí que es podien esborrar: un DELETE és atòmic, igual que un INSERT o un UPDATE. La FK RESTRICT protegeix l'històric de facturació: sense ella, les línies 11 i 39 (detergent) i 23, 32 i 47 (raspall) s'haurien quedat apuntant al buit, i la facturació de 727,95 € deixaria de poder reconstruir-se.

-- 3) La solució correcta: esborrat lògic
BEGIN;

UPDATE productes
SET    actiu = FALSE
WHERE  proveidor_id = 5
  AND  actiu
RETURNING id, nom, preu, stock, actiu;
id nom preu stock actiu
10 Detergent ecològic concentrat 1 L 11.20 70 false
13 Espelmes de cera de soja (pack 2) 13.75 0 false
18 Raspall de dents de bambú 3.50 240 false
UPDATE 3
SELECT COUNT(*) FILTER (WHERE actiu)     AS vigents,
       COUNT(*) FILTER (WHERE NOT actiu) AS retirats,
       COUNT(*)                          AS total
FROM productes;
vigents retirats total
16 4 20
COMMIT;

Tres actualitzats, no quatre: el producte 20 ja estava inactiu, i el filtre AND actiu l'exclou. Això fa la sentència idempotent (05-03): reexecutar-la retornaria UPDATE 0.

Per què aquesta és la solució correcta, en tres punts: conserva l'històric de facturació intacte; manté la integritat referencial sense necessitat de decidir cascades; i és reversible amb un SET actiu = TRUE si EcoNordic torna a subministrar.

-- 4) Catàleg vigent per proveïdor
SELECT pr.id,
       pr.nom AS proveidor,
       pr.pais,
       COUNT(*)              AS productes,
       ROUND(AVG(p.preu), 2) AS preu_mitja,
       SUM(p.stock)          AS stock_total
FROM   productes  AS p
JOIN   proveidors AS pr ON p.proveidor_id = pr.id
WHERE  p.actiu
  AND  pr.actiu
GROUP BY pr.id, pr.nom, pr.pais
ORDER BY productes DESC, pr.id;
id proveidor pais productes preu_mitja stock_total
1 Huerta del Turia Espanya 5 5.74 770
3 Verde Atlántico Portugal 4 12.91 280
4 Maison Nature França 4 9.93 360
2 BioSierra Ibérica Espanya 3 5.27 410

Quatre proveïdors i 16 productes, davant dels cinc proveïdors i 20 productes de la taula. Fixa't en els dos filtres actiu: el del producte i el del proveïdor. Oblidar qualsevol dels dos retornaria un catàleg amb referències que no es poden servir. És exactament el cost de l'esborrat lògic, i exactament el que una vista resoldria (mòdul 10).

Solució 3

1. L'error. ressenyes apareix dues vegades: com a taula destinació del DELETE i dins de l'USING. És la mateixa fallada que l'UPDATE ... FROM de l'exercici 2 de 05-03. PostgreSQL tracta la de l'USING com una instància independent, així que la condició ressenyes.producte_id = productes.id es resol contra ella i la taula destinació queda sense correlacionar. El resultat: si existeix almenys una ressenya d'un producte inactiu, s'esborren totes les ressenyes de la taula. Amb les dades de BotigaVerda no n'hi ha cap, així que n'esborraria 0 — però és pura sort, i tan bon punt n'hi hagués una, s'enduria les dotze.

2. La versió correcta:

-- ✅ CORRECTA
DELETE FROM ressenyes AS r
USING productes AS p
WHERE r.producte_id = p.id
  AND NOT p.actiu
RETURNING r.id, r.producte_id, r.client_id, r.puntuacio;
DELETE 0

Zero files. L'únic producte inactiu és el 20 (Càpsules d'espirulina), i no té cap ressenya — és coherent amb el fet que tampoc no s'hagi venut mai. Comprovació:

SELECT p.id, p.nom, p.actiu, COUNT(r.id) AS ressenyes
FROM   productes AS p
LEFT JOIN ressenyes AS r ON r.producte_id = p.id
WHERE  NOT p.actiu
GROUP BY p.id, p.nom, p.actiu;
id nom actiu ressenyes
20 Càpsules d'espirulina 120 u false 0

3. La versió portable, sense USING:

DELETE FROM ressenyes
WHERE producte_id IN (SELECT id FROM productes WHERE NOT actiu);
DELETE 0

Funciona igual a PostgreSQL, MySQL, SQLite, SQL Server i Oracle. Les subconsultes són el mòdul 7, i aquesta és exactament la raó per la qual s'estudien: USING i FROM són extensions propietàries, la subconsulta és estàndard. Compte amb un detall heretat de 04-02: si la subconsulta pogués retornar algun NULL, un NOT IN donaria zero files silenciosament. Amb IN no hi ha problema, però convé tenir-ho present.

4. És bona idea?

Es guanya Es perd
Menys files a ressenyes L'històric d'opinions sobre el producte
Coherència aparent del catàleg La capacitat de respondre "per què vam retirar aquest producte?"
La mitjana de puntuació històrica de la categoria i del proveïdor
La possibilitat de reactivar el producte conservant-ne les ressenyes

El veredicte és que no, gairebé mai no és bona idea. L'esborrat lògic existeix per conservar informació, i esborrar físicament les ressenyes d'un producte retirat destrueix just el que fa útil la retirada: saber que aquell producte tenia dues estrelles de mitjana i per això es va retirar.

L'alternativa correcta és no esborrar res i filtrar en la lectura: el web mostra les ressenyes de productes vigents, i els informes interns les veuen totes. Un WHERE p.actiu a la consulta pública resol el problema sense destruir ni una sola dada. És la mateixa decisió de disseny de tot l'apartat 9, aplicada un nivell més avall.

I una excepció legítima: si aquelles ressenyes contenien dades personals d'algú que ha exercit el seu dret de supressió, sí que caldria esborrar-les — però llavors el criteri seria el client, no el producte, i ressenyes.client_id ja està declarada ON DELETE CASCADE just per a això.

Conclusió

DELETE tanca la trilogia del DML i planteja la pregunta que les altres dues no plantegen:

  • La sintaxi DELETE FROM taula WHERE ..., amb tot el pes sobre el WHERE, i el protocol de cinc passos reforçat amb un de nou: comptar les files dependents abans d'esborrar.
  • DELETE davant de TRUNCATE: DML contra DDL, WHERE contra tot-o-res, lent contra instantani, amb triggers de fila contra sense, i —crític— transaccional a PostgreSQL però no a MySQL. TRUNCATE no executa les accions ON DELETE: falla si hi ha FK apuntant a la taula.
  • Les tres accions ON DELETE demostrades amb recomptes: RESTRICT impedeix l'esborrat del producte 1 amb el seu is still referenced from table; CASCADE converteix DELETE FROM comandes WHERE id = 6 en quatre files de tres taules informant d'una de sola; SET NULL deixa les quatre comandes de l'Óscar sense comercial (10 → 14 nuls) i tres empleats sense cap.
  • El perill del CASCADE: abast invisible, propagació en cadena, declarat lluny de qui executa. I l'alternativa professional, RESTRICT + esborrat explícit, que costa tres línies i t'ensenya exactament què destrueixes.
  • DELETE ... USING per esborrar segons una altra taula, amb les mateixes regles que UPDATE ... FROM — llevat que aquí un aparellament 1:N no és un problema.
  • RETURNING per veure la fila abans que desaparegui, i el patró INSERT ... SELECT + DELETE dins d'una transacció per arxivar-la de debò.
  • Esborrat lògic davant de físic: productes.actiu i proveidors.actiu són esborrat lògic. Conserva històric, integritat i reversibilitat, a canvi que totes les consultes necessitin WHERE actiu — problema que resolen les vistes del mòdul 10. I la tercera via, data_baixa DATE, que a més et diu quan.
  • Dades personals: actiu = FALSE no compleix el dret de supressió, l'anonimització és el patró habitual quan xoca amb l'obligació comptable, i qualsevol política d'esborrat de dades personals exigeix revisió legal.
  • Recuperació: ROLLBACK si no has confirmat, còpia de seguretat o PITR si sí — i cap de les dues existeix si ningú no la va preparar abans.

Ja saps inserir, modificar i esborrar per separat. Falta l'operació que els sistemes reals necessiten constantment i que cap de les tres no resol: "insereix aquesta fila si no existeix, i actualitza-la si ja existeix". Sincronitzar un catàleg amb el fitxer d'un proveïdor, registrar l'estoc rebut d'un article que potser encara no està donat d'alta, desar la puntuació d'una ressenya que el client pot haver escrit abans. A la lliçó següent, Instrucció UPSERT (MERGE), veuràs per què la solució evident —consultar i després decidir— és incorrecta tan bon punt hi ha dos usuaris alhora, i les dues formes que PostgreSQL ofereix per resoldre-ho en una sola sentència atòmica: INSERT ... ON CONFLICT i el MERGE de l'estàndard.

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