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,DELETEfa desaparèixer la fila sencera — i, ambON 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çarbotigaverda.sql.- Treballa sempre dins de
BEGIN…ROLLBACK/COMMITmentre 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
- La sintaxi, i el protocol reforçat
DELETEsenseWHEREdavant deTRUNCATE- Esborrar una fila referenciada:
RESTRICT - Esborrar una fila referenciada:
CASCADE - Esborrar una fila referenciada:
SET NULL - El perill del
CASCADEi l'alternativa professional DELETE ... USING: esborrar segons una altra taulaRETURNING: conservar el que esborres- Esborrat lògic davant d'esborrat físic
- Dades personals i dret de supressió
- Recuperació després d'un esborrat accidental
- Errors habituals i consells
- Exercicis
- Conclusió
- La sintaxi, i el protocol reforçat
Només dues peces: quina taula i quines files. No hi ha SET, no hi ha valors. Tota la responsabilitat recau sobre el WHERE.
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
DELETE sense WHERE davant de TRUNCATE
DELETE sense WHERE davant de TRUNCATESense WHERE, DELETE buida la taula:
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:
Fan el mateix i no s'assemblen en res:
DELETE FROM taula |
TRUNCATE TABLE taula |
|
|---|---|---|
| Subllenguatge | DML | DDL |
Admet WHERE |
Sí | 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 | Sí | Sí (es pot fer ROLLBACK) |
| Transaccional a MySQL | Sí | No: confirma implícitament |
Dispara TRIGGER de fila |
Sí (BEFORE/AFTER DELETE) |
No (només triggers de sentència) |
| Reinicia la identitat | No | Opcional: TRUNCATE ... RESTART IDENTITY |
| Retorna el nombre de files | Sí (DELETE 12) |
No |
RETURNING |
Sí | 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ó:
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.
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:
Quan fer servir cadascun.
TRUNCATEper buidar taules de treball, d'staging o de proves, on vols començar de zero i la velocitat importa.DELETEper a tota la resta, i sempre que hi hagi unWHEREpel mig. UnDELETE FROM taula;sense condició sobre una taula gran és el pitjor dels dos móns: lent, amb el WAL disparat i sense alliberar espai.
- Esborrar una fila referenciada:
RESTRICT
RESTRICTAquí 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 |
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 sí 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 |
| id | nom | categoria_id | proveidor_id | preu | stock |
|---|---|---|---|---|---|
| 19 | Desodorant natural en barra 50 g | 5 | 4 | 7.80 | 75 |
| productes |
|---|
| 19 |
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é.
- Esborrar una fila referenciada:
CASCADE
CASCADECASCADE 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 |
| 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 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
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 CASCADEConsell: fes
\d taulaabans de qualsevolDELETEsobre una taula que no coneguis al dit. La seccióReferenced byet diu en cinc segons si desencadenaràs una cascada.
- Esborrar una fila referenciada:
SET NULL
SET NULLSET 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 |
| comandes_sense_empleat |
|---|
| 10 |
| id | nom | cognoms | carrec | cap_id | salari |
|---|---|---|---|---|---|
| 4 | Óscar | Peris Blasco | Comercial | 2 | 28500.00 |
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.
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.
| 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 |
| 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.
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 |
- El perill del
CASCADE i l'alternativa professional
CASCADE i l'alternativa professionalCASCADE é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;Compara les dues sortides. Amb CASCADE: DELETE 1, i quatre files fora. Explícitament: DELETE 1, DELETE 2, DELETE 1 — quatre 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.
DELETE ... USING: esborrar segons una altra taula
DELETE ... USING: esborrar segons una altra taulaIgual que UPDATE té FROM, DELETE té USING: permet decidir què esborrar en funció d'una altra taula.
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 |
| linies |
|---|
| 45 |
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 escriuDELETE lc FROM linies_comanda lc JOIN comandes co ON ... WHERE ...(fixa't en l'àlies repetit després deDELETE). SQL Server fa servirDELETE 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.
RETURNING: conservar el que esborres
RETURNING: conservar el que esborresA 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.
| id | comanda_id | motiu | data | import |
|---|---|---|---|---|
| 3 | 13 | El format no correspon al que s'esperava | 2025-10-30 | 19.80 |
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;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.
RETURNINGcompleta la trilogia:INSERT ... RETURNINGet dóna l'idgenerat,UPDATE ... RETURNINGel valor nou,DELETE ... RETURNINGla fila que se'n va. En els tres casos evita unSELECTaddicional i la condició de cursa que porta associada.
- 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.
| id | nom | preu | stock | actiu | data_alta |
|---|---|---|---|---|---|
| 20 | Càpsules d'espirulina 120 u | 16.40 | 55 | false | 2025-06-01 |
| id | nom | pais | 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à.
- 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 | ciutat | pais | data_registre | |
|---|---|---|---|---|---|---|
| 13 | Núria | Bosch Ferrer | [email protected] | Barcelona | Espanya | 2025-06-20 |
| clients |
|---|
| 14 |
Però amb un client que sí que ha comprat, el conflicte apareix immediatament:
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;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
DELETEmal 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.
- 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 aUPDATE, amb conseqüències pitjors. - Refiar-se del
DELETE NambCASCADE. Compta les files que esborres tu, no les que esborra el motor en cadena.DELETE 1pot ser quatre files, o quatre milions. - No comprovar què depèn de la fila abans d'esborrar-la.
\d taulai la seva seccióReferenced byen cinc segons. - Fer servir
TRUNCATEcreient que és unDELETEràpid. No admetWHERE, 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
TRUNCATEsigui transaccional. A PostgreSQL sí; a MySQL no, i allà no hi haROLLBACKpossible. - Repetir la taula destinació a l'
USING. Producte cartesià silenciós, igual que aUPDATE ... FROM. - Esborrar registres amb històric comercial. Per a això hi ha
actiu = FALSE. La FKRESTRICTintentarà frenar-te; no l'esquivis ambCASCADE. - 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 = FALSEcompleix 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 qualsevolDELETE. Vols veure la fila completa que desapareixerà, no només el seuid. - Consell:
RETURNING *sempre en unDELETE. 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 aCASCADE. Tres línies en lloc d'una, a canvi de veure exactament què destrueixes. - Consell: en dissenyar, fes servir
data_baixa DATEen 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 BEGIN … ROLLBACK 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).
- Comprova quins són i quins d'ells s'han venut alguna vegada.
- Intenta l'esborrat físic de tots i explica què passa.
- Proposa i aplica la solució correcta, justificant per què és la correcta.
- 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;- Troba l'error i explica què faria realment.
- Escriu-la correctament i digues quantes files esborraria sobre la base acabada de recarregar.
- Reescriu-la de manera portable, sense
USING, indicant quin mòdul cobreix aquella tècnica. - 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 |
| 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ú:
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:
| 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.
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 |
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 |
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;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:
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 elWHERE, i el protocol de cinc passos reforçat amb un de nou: comptar les files dependents abans d'esborrar. DELETEdavant deTRUNCATE: DML contra DDL,WHEREcontra tot-o-res, lent contra instantani, amb triggers de fila contra sense, i —crític— transaccional a PostgreSQL però no a MySQL.TRUNCATEno executa les accionsON DELETE: falla si hi ha FK apuntant a la taula.- Les tres accions
ON DELETEdemostrades amb recomptes:RESTRICTimpedeix l'esborrat del producte 1 amb el seuis still referenced from table;CASCADEconverteixDELETE FROM comandes WHERE id = 6en quatre files de tres taules informant d'una de sola;SET NULLdeixa 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 ... USINGper esborrar segons una altra taula, amb les mateixes regles queUPDATE ... FROM— llevat que aquí un aparellament 1:N no és un problema.RETURNINGper veure la fila abans que desaparegui, i el patróINSERT ... SELECT+DELETEdins d'una transacció per arxivar-la de debò.- Esborrat lògic davant de físic:
productes.actiuiproveidors.actiusón esborrat lògic. Conserva històric, integritat i reversibilitat, a canvi que totes les consultes necessitinWHERE 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 = FALSEno 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ó:
ROLLBACKsi 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
- 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
