Fins ara hem mirat les taules d'una en una. Però el poder real d'una base de dades relacional no és a les taules: és a les relacions entre elles. Aquesta lliçó respon la pregunta que tot principiant es fa en veure l'esquema de BotigaVerda: "per què nou taules i no una de sola amb tot?". Veuràs què és una clau primària i per què el curs fa servir id numèrics, com una clau forana impedeix físicament que existeixi una comanda d'un client inexistent, què passa quan intentes esborrar un registre del qual depenen d'altres, com es representen les cardinalitats 1:1, 1:N i N:M, i com la normalització descompon una taula monolítica en un conjunt de taules sanes. És la lliçó més conceptual del mòdul i també la que més rendiment et donarà quan arribis als JOIN.

Contingut

  1. El model relacional en 10 minuts
  2. Clau primària: natural davant de subrogada
  3. Claus candidates i claus úniques
  4. Clau forana i integritat referencial
  5. Què passa en esborrar o modificar un pare: ON DELETE i ON UPDATE
  6. Cardinalitats: 1:1, 1:N i N:M
  7. Normalització pràctica: 1FN, 2FN i 3FN
  8. Quan desnormalitzar a propòsit
  9. Errors habituals i consells
  10. Exercicis
  11. Conclusió

  1. El model relacional en 10 minuts

El model relacional el va proposar Edgar F. Codd el 1970 i descansa en tres idees sorprenentment simples.

Relació, tupla i domini

Concepte formal Nom col·loquial Què és Exemple a BotigaVerda
Relació Taula Un conjunt de tuples amb la mateixa estructura productes
Tupla Fila Un element concret d'aquest conjunt El producte "Mel de tarongina crua 500 g"
Atribut Columna Una propietat de la tupla preu
Domini Tipus (més restriccions) El conjunt de valors vàlids per a un atribut NUMERIC(10,2) ≥ 0
Grau Nombre de columnes Quants atributs té la relació productes té grau 9
Cardinalitat Nombre de files Quantes tuples conté productes té 20 files

Nota important: "relació" no significa "relació entre taules". En la terminologia de Codd, una relació és una taula. Les connexions entre taules s'anomenen associacions o, a la pràctica, s'implementen mitjançant claus foranes. La coincidència de noms confon molta gent.

Les tres propietats que ho canvien tot

  1. Una relació és un conjunt, així que no hi ha ordre ni duplicats conceptuals. Si dues files fossin idèntiques en tot, serien la mateixa tupla.
  2. Les dades es relacionen pel seu valor, no per punters. A BotigaVerda, comandes.client_id = 7 assenyala el client 7 perquè el valor coincideix, no perquè hi hagi una adreça de memòria desada. Aquesta idea, que avui sembla òbvia, era revolucionària davant dels sistemes jeràrquics i de xarxa dels anys 60.
  3. L'estructura és independent de l'accés. Pots reorganitzar índexs i emmagatzematge sense canviar ni una consulta.

De la propietat 2 en surt directament tot el que veuràs al mòdul 3: un JOIN no és més que aparellar files els valors de les quals coincideixen.

  1. Clau primària: natural davant de subrogada

Una clau primària (PK) és la columna —o combinació de columnes— que identifica de manera única cada fila d'una taula. Les seves tres propietats:

  • Única: no es pot repetir.
  • No nul·la: mai no pot ser NULL.
  • Estable: idealment no hauria de canviar mai.

Hi ha dues filosofies per triar-la:

Tipus Què és Exemple Avantatges Inconvenients
Natural Una dada real del negoci que ja és única email a clients, un ISBN, un NIF Significativa; sense columnes extra; evita duplicats per disseny Pot canviar (una persona canvia d'email); sol ser llarga (text), cosa que encareix els índexs i les claus foranes
Subrogada Un identificador artificial sense significat id INTEGER autoincremental, UUID Curta, estable, uniforme, ràpida en índexs i JOIN; no canvia mai No significa res; obliga a afegir un UNIQUE a part per a la clau real de negoci

Per què aquest curs fa servir id subrogat a les nou taules

-- Totes les taules de BotigaVerda segueixen el mateix patró
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY

Les raons:

  1. Uniformitat. Saps que qualsevol taula s'identifica per id, i que qualsevol clau forana es diu <taula_singular>_id. Zero sorpreses.
  2. Estabilitat. L'email d'un client pot canviar; el seu id no. Si l'email fos la PK i canviés, caldria actualitzar en cascada totes les taules que el referencien.
  3. Eficiència. Un INTEGER ocupa 4 bytes; un email, 30 o 40. Tots els índexs i totes les claus foranes se'n beneficien.
  4. Llegibilitat didàctica. WHERE client_id = 7 és infinitament més còmode en un curs que WHERE client_email = '[email protected]'.

Important: fer servir id subrogat no eximeix de declarar la clau natural com a UNIQUE. A BotigaVerda, clients.email és UNIQUE encara que la PK sigui id: sense aquest UNIQUE podries registrar dues vegades el mateix client i la base de dades no protestaria.

Clau primària composta

Res no obliga que la PK sigui una sola columna. Es podria formar amb diverses:

-- Alternativa NO triada per a linies_comanda
PRIMARY KEY (comanda_id, producte_id)

Això significaria "un producte només pot aparèixer una vegada a cada comanda". És una decisió legítima, però BotigaVerda fa servir un id propi a linies_comanda per uniformitat i perquè permet que un mateix producte aparegui en dues línies de la mateixa comanda amb preus o descomptes diferents.

  1. Claus candidates i claus úniques

  • Una clau candidata és qualsevol conjunt de columnes que identifica unívocament una fila. Una taula en pot tenir diverses.
  • La clau primària és la candidata que tries com a identificador oficial.
  • La resta de candidates es declaren com a claus úniques (UNIQUE).

A clients tenim dues candidates:

Candidata Triada com a PK? Com es declara
id PRIMARY KEY
email No UNIQUE

Diferència clau entre PRIMARY KEY i UNIQUE:

Aspecte PRIMARY KEY UNIQUE
Admet NULL? No, mai Sí (i a PostgreSQL, diversos nuls alhora)
Quantes per taula? Una Les que vulguis
Pot ser destí d'una FK?
Índex Es crea automàticament Es crea automàticament

Aquest detall dels NULL a UNIQUE sorprèn: PostgreSQL considera que dos nuls no són iguals entre si, així que una columna UNIQUE pot tenir moltes files amb NULL. Si necessites el contrari, PostgreSQL 15 va introduir UNIQUE NULLS NOT DISTINCT.

  1. Clau forana i integritat referencial

Una clau forana (FK) és una columna que conté valors que han d'existir a la clau primària d'una altra taula. És el mecanisme que connecta les taules i, sobretot, el que garanteix que no hi hagi dades òrfenes.

A BotigaVerda:

-- Fragment conceptual (la sintaxi completa és del mòdul 5)
CREATE TABLE comandes (
    id         INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    client_id  INTEGER NOT NULL REFERENCES clients(id),
    empleat_id INTEGER     NULL REFERENCES empleats(id),
    ...
);

Es llegeix: "client_id ha de correspondre a un id existent a clients, i és obligatori; empleat_id també ha d'existir a empleats, però pot quedar buit".

Això s'anomena integritat referencial: la base de dades garanteix que les referències apunten a alguna cosa real. I no és una recomanació, és una barrera física.

Quin error dona PostgreSQL en inserir una FK inexistent

BotigaVerda té 15 clients. Si intentes registrar una comanda del client 999:

INSERT INTO comandes (client_id, data_comanda, estat, metode_pagament, despeses_enviament)
VALUES (999, '2026-03-01', 'pendent', 'targeta', 4.95);
ERROR:  insert or update on table "comandes" violates foreign key constraint "comandes_client_id_fkey"
DETAIL:  Key (client_id)=(999) is not present in table "clients".

Llegeix-lo amb atenció, perquè el veuràs moltes vegades:

  • violates foreign key constraint → has trencat una FK.
  • El nom comandes_client_id_fkey segueix el patró <taula>_<columna>_fkey i et diu exactament quina.
  • El DETAIL et dona el valor culpable (999) i la taula on hauria d'existir.

Sense aquesta restricció tindries una comanda fantasma: apareixeria al total de vendes, però en intentar mostrar el nom del client no hi hauria res. Aquest tipus d'inconsistència és demolidor en un sistema real, i és impossible d'introduir aquí.

Quin error dona en esborrar un pare referenciat

La clienta 1 (Lucía Martínez Soler) té comandes. Si intentes esborrar-la:

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

PostgreSQL s'hi nega: esborrar aquesta clienta deixaria comandes apuntant al buit. Aquest comportament per defecte s'anomena RESTRICT (tècnicament NO ACTION, que és equivalent tret de les transaccions diferides), i és exactament el que vols la majoria de les vegades.

  1. Què passa en esborrar o modificar un pare: ON DELETE i ON UPDATE

En declarar una FK pots triar què ha de passar quan la fila referenciada s'esborra (ON DELETE) o canvia la seva clau (ON UPDATE).

Acció Comportament en esborrar el pare
NO ACTION (per defecte) Rebutja l'operació amb error
RESTRICT Rebutja immediatament, sense esperar al final de la transacció
CASCADE Esborra també totes les files filles
SET NULL Deixa la FK de les files filles a NULL (requereix que la columna admeti nuls)
SET DEFAULT Posa el valor per defecte de la columna filla

Aplicat a BotigaVerda, l'elecció no és arbitrària: cada relació demana una acció diferent segons el significat de negoci.

Relació Acció triada Per què
comandes.client_id → clients.id RESTRICT Una comanda no es pot quedar sense client. Abans d'esborrar un client cal decidir què fer amb el seu historial
comandes.empleat_id → empleats.id SET NULL Si un comercial deixa l'empresa, la comanda continua sent vàlida: simplement passa a no tenir comercial assignat, igual que les comandes web
linies_comanda.comanda_id → comandes.id CASCADE Una línia no existeix sense la seva comanda. Esborrar la comanda se n'ha de dur les línies: en formen part
linies_comanda.producte_id → productes.id RESTRICT Mai no has d'esborrar un producte que s'ha venut: destruiries l'històric de facturació. Per retirar-lo es fa servir actiu = FALSE
clients.referit_per_id → clients.id SET NULL Si s'esborra qui va referir, el referit continua sent client; només perd aquesta informació
empleats.cap_id → empleats.id SET NULL Si un cap se'n va, el seu equip queda temporalment sense cap assignat, no s'esborra
ressenyes.producte_id → productes.id CASCADE Si el producte desaparegués del catàleg, les seves ressenyes no tenen sentit
devolucions.comanda_id → comandes.id CASCADE Una devolució és un fet associat a una comanda concreta

Regla mental per decidir: pregunta't "la fila filla té sentit per si sola si el pare desapareix?". Si no en té, CASCADE. Si en té però perd una relació opcional, SET NULL. Si el pare no hauria de poder desaparèixer estant referenciat, RESTRICT.

Compte amb CASCADE. És còmode i perillós: un sol DELETE es pot propagar per mitja base de dades en silenci. Fes-lo servir únicament quan la relació sigui de composició real (la part no viu sense el tot), com linies_comanda respecte a comandes.

ON UPDATE funciona igual, però es dispara quan canvia la clau primària del pare. Amb claus subrogades gairebé mai no es fa servir, perquè un id autoincremental no canvia mai. És precisament un dels avantatges de les claus subrogades davant de les naturals.

La sintaxi completa per declarar aquestes restriccions (CONSTRAINT ... FOREIGN KEY ... REFERENCES ... ON DELETE CASCADE) s'estudia a la lliçó 05-01. Aquí el que importa és entendre el criteri de decisió.

  1. Cardinalitats: 1:1, 1:N i N:M

La cardinalitat descriu quantes files d'una taula es poden associar amb quantes d'una altra.

6.1. Un a molts (1:N)

És la més freqüent. Una categoria té molts productes; cada producte pertany a una sola categoria.

erDiagram
    CATEGORIES ||--o{ PRODUCTES : "classifica"
    CATEGORIES {
        int id PK
        varchar nom
        text descripcio
    }
    PRODUCTES {
        int id PK
        varchar nom
        int categoria_id FK
        numeric preu
        int stock
    }

Com s'implementa: la FK va sempre al costat "molts". productes.categoria_id apunta a categories.id. Mai al revés: si posessis una columna producte_id a categories, només hi cabria un producte per categoria.

Relacions 1:N a BotigaVerda:

Costat "un" Costat "molts" Columna FK
categories productes productes.categoria_id
proveidors productes productes.proveidor_id
clients comandes comandes.client_id
empleats comandes comandes.empleat_id
comandes linies_comanda linies_comanda.comanda_id
productes linies_comanda linies_comanda.producte_id
productes ressenyes ressenyes.producte_id
clients ressenyes ressenyes.client_id
comandes devolucions devolucions.comanda_id

6.2. Molts a molts (N:M) i la taula pont

Una comanda conté molts productes, i un producte apareix en moltes comandes. Una relació N:M no es pot implementar directament: no hi ha on posar la FK. La solució és una taula pont (o taula intermèdia, o d'associació).

erDiagram
    COMANDES ||--o{ LINIES_COMANDA : "conté"
    PRODUCTES ||--o{ LINIES_COMANDA : "apareix a"
    COMANDES {
        int id PK
        int client_id FK
        date data_comanda
        varchar estat
    }
    LINIES_COMANDA {
        int id PK
        int comanda_id FK
        int producte_id FK
        int quantitat
        numeric preu_unitari
        numeric descompte
    }
    PRODUCTES {
        int id PK
        varchar nom
        numeric preu
    }

La N:M entre comandes i productes es descompon en dues relacions 1:N que convergeixen a linies_comanda.

I aquí hi ha el detall que distingeix una taula pont ben dissenyada: linies_comanda no és només un enllaç, té dades pròpies.

Columna Per què hi és
quantitat Quantes unitats d'aquest producte en aquesta comanda? Només té sentit a la intersecció
preu_unitari El preu en el moment de la venda. Si el producte puja de preu demà, les factures antigues no han de canviar
descompte La rebaixa aplicada a aquesta línia concreta

Aquest preu_unitari és un exemple perfecte de desnormalització deliberada (secció 8): duplica informació que també és a productes.preu, però és imprescindible perquè són coses diferents: un és el preu actual i l'altre el preu històric facturat.

6.3. Un a un (1:1)

Cada fila d'A es correspon amb com a màxim una de B. S'implementa posant una FK en una de les dues taules i declarant-la a més UNIQUE.

BotigaVerda no té relacions 1:1, perquè gairebé mai no calen: si la correspondència és exacta, el natural és fondre totes dues taules en una. Els casos legítims són tres:

  • Separar columnes grans poc consultades (una taula productes_fitxa_tecnica amb textos enormes).
  • Aïllar dades sensibles amb permisos diferents (una taula empleats_dades_bancaries).
  • Especialització: una taula usuaris general i taules usuaris_admin / usuaris_client amb camps exclusius.

6.4. Relacions reflexives

Una taula es pot referenciar a si mateixa. BotigaVerda té dos casos:

Relació Significat Cardinalitat
empleats.cap_id → empleats.id Jerarquia organitzativa 1:N (un cap, molts subordinats)
clients.referit_per_id → clients.id Programa de recomanació 1:N (un client en refereix diversos)

Totes dues columnes admeten NULL, i aquest nul té un significat precís: cap_id IS NULL identifica la direcció general (l'empleada 1, Rosa Alcázar Vives) i referit_per_id IS NULL identifica els clients que van arribar pel seu compte. Consultar aquestes relacions requereix SELF JOIN, que s'estudia a la lliçó 03-06.

6.5. Resum

Cardinalitat Com s'implementa Exemple a BotigaVerda
1:N FK al costat "molts" productes.categoria_id
N:M Taula pont amb dues FK linies_comanda
1:1 FK amb restricció UNIQUE (no s'aplica)
Reflexiva FK a la mateixa taula, normalment nul·lable empleats.cap_id

  1. Normalització pràctica: 1FN, 2FN i 3FN

La normalització és el procés d'organitzar les columnes en taules per eliminar redundància i evitar anomalies. Sona acadèmic, però s'entén millor veient què passa quan no es fa.

El punt de partida: una taula desnormalitzada

Imagina't que BotigaVerda desés totes les seves comandes en una única taula:

comanda_id data client_nom client_email client_ciutat productes categoria preu_total
1 2025-03-04 Lucía Martínez [email protected] València Oli oliva, Arròs integral, Infusió camamilla Alimentació, Alimentació, Begudes 43.20
5 2025-05-07 Lucía Martínez [email protected] València Detergent eco, Lufa, Bosses cotó Llar sostenible 32.10
8 2025-06-28 Sofia Moreira [email protected] Lisboa Oli oliva, Mel tarongina, Infusió camamilla Alimentació 66.87

Els problemes són immediats:

Anomalia Què passa aquí
D'inserció No pots donar d'alta un client que encara no ha demanat res, ni un producte que encara no s'ha venut
D'actualització Si la Lucía canvia d'email, cal modificar-lo a totes les seves files. Si en falles una, tindràs dos emails contradictoris
D'esborrat Si esborres la comanda 8, perds tota la informació de la Sofia Moreira
De redundància Les dades de la Lucía es repeteixen a cada comanda: malbaratament d'espai i font constant d'incoherències
De consulta Quantes unitats d'"Oli oliva" s'han venut? Impossible: és dins d'una llista separada per comes

Primera forma normal (1FN)

Regla: cada cel·la conté un únic valor atòmic; no hi ha grups repetits.

La columna productes viola la 1FN descaradament: conté tres valors en una cel·la. Per arreglar-ho cal treure els productes a files pròpies.

Senyals que alguna cosa viola la 1FN:

  • Llistes separades per comes en una cel·la.
  • Columnes numerades: producte_1, producte_2, producte_3.
  • Un camp que de vegades conté una dada i de vegades diverses.

Després d'aplicar la 1FN, cada producte de cada comanda és una fila, cosa que ja ens dona el germen de linies_comanda.

Segona forma normal (2FN)

Regla: estar en 1FN i que cap atribut no clau no depengui només d'una part d'una clau primària composta.

Després de la 1FN, la clau de la nostra taula seria (comanda_id, producte_nom). Però fixa-t'hi:

  • data depèn només de comanda_id, no del producte.
  • client_nom, client_email i client_ciutat depenen només de comanda_id.
  • categoria depèn només de producte_nom, no de la comanda.

Són dependències parcials, i provoquen que aquestes dades es repeteixin una vegada per cada línia de la comanda. La solució és dividir:

  • El que depèn de la comanda → taula comandes.
  • El que depèn del producte → taula productes.
  • El que depèn de la combinació (quantitat, preu de venda, descompte) → taula linies_comanda.

Aquí tens, deduïda, la taula pont de la secció 6.2.

Tercera forma normal (3FN)

Regla: estar en 2FN i que cap atribut no clau no depengui d'un altre atribut no clau (res de dependències transitives).

A la taula comandes resultant continuaríem tenint client_nom, client_email i client_ciutat. Aquests depenen de client_email (o del client en general), no de comanda_id. És una dependència transitiva: comanda_id → client → email.

La solució: extreure una taula clients i deixar a comandes només la referència client_id. Idènticament, categoria a productes depèn de la categoria, no del producte: s'extreu categories i queda productes.categoria_id.

El resultat

graph LR
    A["Taula única<br/>desnormalitzada"] -->|1FN: valors atòmics| B["comandes_linies<br/>una fila per producte"]
    B -->|2FN: separar dependències parcials| C["comandes + productes<br/>+ linies_comanda"]
    C -->|3FN: eliminar transitives| D["+ clients + categories<br/>+ proveidors…"]

Aplicant aquestes tres regles al cas de BotigaVerda arribes, gairebé mecànicament, a l'esquema de nou taules del curs. Aquesta és la resposta a la pregunta inicial: l'esquema no està partit per caprici, sinó perquè cada taula agrupa exactament les dades que depenen d'una mateixa cosa.

Resum memoritzable de les tres formes normals:

Forma Regla en una frase Violació típica
1FN Un valor per cel·la, sense grups repetits "Oli, Arròs, Infusió" en una columna
2FN Sense dependències parcials d'una clau composta data_comanda repetida a cada línia
3FN Sense dependències transitives entre atributs no clau client_email dins de comandes

La regla mnemotècnica clàssica: "cada atribut no clau ha de dependre de la clau, de tota la clau i de res més que la clau". La primera part és 1FN/2FN, "de tota la clau" és 2FN i "de res més que la clau" és 3FN.

Hi ha formes normals superiors (BCNF, 4FN, 5FN) que resolen casos més estranys. A la pràctica professional, arribar a 3FN cobreix el 95 % dels dissenys.

  1. Quan desnormalitzar a propòsit

La normalització optimitza la integritat i l'escriptura. De vegades es paga un preu en velocitat de lectura, perquè reconstruir una factura obliga a combinar cinc taules. Desnormalitzar és introduir redundància conscientment a canvi de rendiment o de correcció històrica.

Casos legítims, amb exemples de BotigaVerda:

Cas Exemple Per què està justificat
Dades històriques immutables linies_comanda.preu_unitari El preu facturat no ha de canviar quan canviï productes.preu. No és redundància: són dades diferents
Agregats precalculats Una columna comandes.total Evita recalcular la suma de línies a cada consulta. Cost: cal mantenir-la sincronitzada (amb disparadors, mòdul 10)
Còpia d'un atribut molt consultat Desar client_pais a comandes Evita un JOIN en informes que agrupen per país. Només si el volum ho justifica
Taules d'informes Una taula resum de vendes mensuals Els magatzems de dades fan servir esquemes en estrella deliberadament desnormalitzats

I la regla d'or:

Normalitza primer. Desnormalitza després, amb mesures a la mà, i documenta per què.

Desnormalitzar sense mesurar és la causa número u de bases de dades incoherents. Cada dada duplicada és una dada que pot quedar desincronitzada, i necessitaràs un mecanisme explícit (disparador, procés batch, lògica d'aplicació) per mantenir-la al dia. Abans de desnormalitzar, prova amb un índex (mòdul 8) o una vista materialitzada (mòdul 10): solen resoldre el problema sense cost d'integritat.

Errors habituals i consells

  • Confondre "relació" amb "relació entre taules". En el model de Codd, una relació és una taula.
  • Posar la FK al costat equivocat d'una 1:N. Sempre va al costat "molts". Si la poses a l'"un", limites la relació a un únic fill.
  • Intentar fer una N:M sense taula pont. Desar "3,7,12" en una columna productes_ids viola la 1FN, impedeix les FK i fa les consultes impossibles.
  • Fer servir CASCADE per comoditat. Un DELETE es pot propagar molt més lluny del que et penses. Reserva CASCADE per a relacions de composició real.
  • Esborrar productes venuts. Destrueix l'històric i salta la FK. Fes servir actiu = FALSE (esborrat lògic); per això existeix la columna.
  • Oblidar el UNIQUE de la clau natural. Amb id subrogat com a PK, res no impedeix duplicar l'email d'un client si no el declares UNIQUE.
  • Sobrenormalitzar. Partir una taula en set per purisme acadèmic complica cada consulta sense aportar integritat real.
  • Desnormalitzar "per si de cas". Sense un mesurament que ho justifiqui, només estàs creant incoherències futures.
  • Consell: indexa les teves claus foranes. PostgreSQL crea índex automàticament per a la PK, però no per a les FK. Sense aquest índex, els JOIN i els esborrats en cascada poden ser molt lents (mòdul 8).
  • Consell: dibuixa el diagrama abans d'escriure DDL. Deu minuts d'esquema en paper eviten setmanes de migracions.
  • Consell: anomena les FK amb el patró <taula_singular>_id. client_id, producte_id, comanda_id. La consistència fa que les consultes s'escriguin gairebé soles.

Exercicis

Exercici 1

Per a cada parell de taules de BotigaVerda, indica la cardinalitat (1:1, 1:N o N:M), on va la clau forana i quina acció ON DELETE triaries, justificant-la:

  1. proveidors i productes
  2. clients i ressenyes
  3. comandes i devolucions
  4. clients i productes (a través de ressenyes)
  5. empleats amb si mateixa

Exercici 2

Aquesta taula viola les tres formes normals. Identifica quina regla trenca en cada cas i descompon-la en taules normalitzades fins a 3FN, indicant claus primàries i foranes.

ressenya_id producte preu_producte categoria client_email client_ciutat puntuacions dates
1 Oli d'oliva 12.50 Alimentació [email protected] València 5, 4 2025-03-15, 2025-04-02
2 Crema àloe vera 18.90 Cosmètica natural [email protected] València 5 2025-03-25

Exercici 3

Prediu què respon PostgreSQL a cadascuna d'aquestes operacions sobre la base de BotigaVerda ja carregada, i explica per què:

-- a)
INSERT INTO ressenyes (producte_id, client_id, puntuacio, comentari, data)
VALUES (99, 1, 5, 'Excel·lent', '2026-03-01');

-- b)
DELETE FROM productes WHERE id = 1;

-- c)
DELETE FROM comandes WHERE id = 20;

-- d)
UPDATE empleats SET cap_id = NULL WHERE id = 4;

Solucions

Solució 1

# Parell Cardinalitat On va la FK ON DELETE Justificació
1 proveidorsproductes 1:N productes.proveidor_id RESTRICT Un proveïdor serveix molts productes. No has d'esborrar un proveïdor els productes del qual continuen al catàleg; es marca actiu = FALSE
2 clientsressenyes 1:N ressenyes.client_id CASCADE Un client escriu moltes ressenyes. Si s'exerceix el dret de supressió del client, les seves ressenyes se n'han d'anar amb ell
3 comandesdevolucions 1:N devolucions.comanda_id CASCADE Una comanda pot tenir diverses devolucions parcials. Una devolució no existeix sense la seva comanda
4 clientsproductes N:M Taula pont ressenyes (amb client_id i producte_id) Segons cada costat Un client ressenya molts productes i un producte rep moltes ressenyes. ressenyes és taula pont amb dades pròpies: puntuació, comentari i data
5 empleatsempleats 1:N reflexiva empleats.cap_id SET NULL Un cap té diversos subordinats. Si el cap deixa l'empresa, l'equip queda sense cap assignat però no s'esborra

Solució 2

Violacions:

Forma Què es trenca
1FN puntuacions i dates contenen llistes separades per comes
2FN Després de separar cada ressenya a la seva fila, preu_producte i categoria depenen només del producte, no de la ressenya
3FN client_ciutat depèn de client_email (del client), no de ressenya_id; i categoria és una entitat pròpia, no un atribut del producte

Descomposició en 3FN:

categories(id PK, nom)
productes(id PK, nom, preu, categoria_id FK → categories.id)
clients(id PK, email UNIQUE, ciutat)
ressenyes(id PK, producte_id FK → productes.id, client_id FK → clients.id,
          puntuacio, data)

I així queda:

Taula Files resultants
categories Alimentació, Cosmètica natural
productes Oli d'oliva (12.50, Alimentació), Crema àloe vera (18.90, Cosmètica natural)
clients lucia.martinez@… (València), carlos.ferrer@… (València)
ressenyes 3 files: (Oli, Lucía, 5, 2025-03-15), (Oli, Lucía, 4, 2025-04-02), (Crema, Carlos, 5, 2025-03-25)

Fixa't que la llista "5, 4" de la primera fila es converteix en dues ressenyes diferents: la 1FN ens ha obligat a descobrir que allà hi havia realment dos fets, no un.

Solució 3

a) Falla:

ERROR:  insert or update on table "ressenyes" violates foreign key constraint "ressenyes_producte_id_fkey"
DETAIL:  Key (producte_id)=(99) is not present in table "productes".

BotigaVerda té 20 productes, així que el 99 no existeix. La integritat referencial impedeix crear una ressenya òrfena.

b) Falla:

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".

El producte 1 (Oli d'oliva verge extra) apareix en diverses línies de comanda, i aquesta FK està declarada RESTRICT precisament per protegir l'històric de facturació. Per retirar-lo del catàleg es fa UPDATE productes SET actiu = FALSE WHERE id = 1;.

c) Funciona, i esborra més del que sembla:

DELETE 1

S'elimina la comanda 20 i, en cascada, les seves dues línies de comanda (linies_comanda.comanda_id està declarada ON DELETE CASCADE). És l'exemple perfecte de per què CASCADE s'ha de fer servir amb cura: una sola sentència ha esborrat tres files en dues taules. Si la comanda tingués devolucions associades, també desapareixerien.

d) Funciona:

UPDATE 1

L'empleat 4 (Óscar Peris Blasco, comercial) passa a no tenir cap assignat. La columna cap_id admet NULL per disseny, així que no es viola cap restricció. Ara hi hauria dos empleats amb cap_id IS NULL: la directora general (que ho és per naturalesa) i aquest comercial (que ho és per un canvi organitzatiu). És un bon recordatori que NULL pot significar coses diferents en files diferents, i de per què convé documentar-ne la semàntica.

Conclusió

Aquesta lliçó explica el perquè de l'esquema que carregaràs a continuació:

  • El model relacional organitza les dades en relacions (taules) de tuples (files) amb atributs (columnes) sobre dominis (tipus), i connecta la informació per valor, no per punters: d'aquí neixen els JOIN.
  • La clau primària identifica cada fila de manera única, no nul·la i estable. BotigaVerda fa servir id subrogat a les nou taules per uniformitat, estabilitat i eficiència, sense renunciar a declarar UNIQUE les claus naturals com clients.email.
  • La clau forana garanteix la integritat referencial: PostgreSQL rebutja amb violates foreign key constraint tant inserir una referència inexistent com esborrar un pare referenciat.
  • Les accions ON DELETE (RESTRICT, CASCADE, SET NULL) es trien segons el significat de negoci: CASCADE per a linies_comanda, SET NULL per a comandes.empleat_id, RESTRICT per a productes venuts.
  • Les cardinalitats 1:N (FK al costat "molts"), N:M (taula pont com linies_comanda, amb dades pròpies) i les relacions reflexives d'empleats.cap_id i clients.referit_per_id.
  • La normalització fins a 3FN, deduïda a partir d'una taula de comandes monolítica, explica per què l'esquema té nou taules; i la desnormalització deliberada justifica que linies_comanda.preu_unitari conservi el preu històric.

A la lliçó següent, La base de dades del curs: BotigaVerda, tot això es fa tangible: veuràs el diagrama entitat-relació complet, la descripció taula per taula amb les seves columnes i tipus, i l'script SQL a punt per copiar que crea les nou taules i carrega les dades que faràs servir durant els onze mòduls restants. En acabar-la tindràs la base de dades funcionant al teu equip, i a partir del mòdul 2 començaràs a consultar-la de debò.

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