Arriba el moment de convertir la teoria en una cosa tangible. En aquesta lliçó coneixeràs a fons BotigaVerda, la botiga en línia fictícia de productes ecològics que serà l'escenari de tots els exemples i exercicis dels onze mòduls restants. En veuràs el model de negoci, el diagrama entitat-relació complet, la descripció de les seves nou taules columna a columna i —el més important— l'script SQL a punt per copiar i executar que crea les taules i carrega les dades. En acabar tindràs la base de dades funcionant al teu equip i sabràs què hi ha a dins, de manera que a partir del mòdul 2 podràs concentrar-te a aprendre SQL en comptes d'entendre el context.

Aquesta lliçó no és un tutorial de DDL: veuràs l'script i entendràs què fa cada bloc, però la sintaxi completa de CREATE TABLE, els tipus de restricció i les migracions s'estudien al mòdul 5.

Contingut

  1. El negoci: què és BotigaVerda i com opera
  2. Diagrama entitat-relació
  3. Les nou taules, una a una
  4. Decisions de disseny que convé conèixer
  5. L'script de creació de taules
  6. L'script de càrrega de dades
  7. Com carregar la base de dades
  8. Consultes de verificació
  9. Errors habituals i consells
  10. Exercicis
  11. Conclusió

  1. El negoci: què és BotigaVerda i com opera

BotigaVerda és una botiga en línia de productes ecològics amb seu a València, fundada a finals del 2024 i operativa des de principis del 2025.

Què ven. Un catàleg d'una vintena de referències repartides en sis categories: alimentació ecològica, cosmètica natural, llar sostenible, begudes, higiene personal i complements alimentaris. Compra a cinc proveïdors d'Espanya, Portugal, França i Alemanya.

A qui ven. Particulars d'Espanya, Portugal i França. Molts clients arriben per recomanació d'un altre client, cosa que l'empresa registra per al seu programa de referits.

Com opera.

  • Dos canals de venda. Les comandes que entren per la web no tenen comercial assignat; les que entren per telèfon les gestiona un comercial de l'equip. Aquesta distinció és la raó que comandes.empleat_id pugui ser NULL, i serà protagonista de les lliçons de LEFT JOIN i de valors nuls.
  • Un equip de vuit persones amb jerarquia: direcció general, dos responsables d'àrea, comercials, atenció al client, magatzem i anàlisi de dades.
  • Cicle de vida de la comanda: pendentpagatenviatlliurat, amb la possibilitat de cancellat en qualsevol punt.
  • Quatre mètodes de pagament: targeta, transferència, PayPal i contrareemborsament.
  • Despeses d'enviament variables segons destinació, amb enviament gratuït a partir de cert import.
  • Ressenyes de clients amb puntuació d'1 a 5 sobre els productes que han comprat.
  • Devolucions associades a una comanda, amb motiu i import reemborsat.

Preguntes que la direcció es fa cada mes (i que sabràs respondre en acabar el curs): quins productes es venen més?, quin país deixa més marge?, quins clients no compren des de fa mesos?, quin comercial tanca més comandes?, quins productes acumulen males ressenyes?, quant ens costen les devolucions?

  1. Diagrama entitat-relació

erDiagram
    CATEGORIES   ||--o{ PRODUCTES      : "classifica"
    PROVEIDORS   ||--o{ PRODUCTES      : "subministra"
    CLIENTS      ||--o{ COMANDES       : "fa"
    EMPLEATS     ||--o{ COMANDES       : "gestiona"
    COMANDES     ||--o{ LINIES_COMANDA : "conté"
    PRODUCTES    ||--o{ LINIES_COMANDA : "apareix a"
    PRODUCTES    ||--o{ RESSENYES      : "rep"
    CLIENTS      ||--o{ RESSENYES      : "escriu"
    COMANDES     ||--o{ DEVOLUCIONS    : "origina"
    CLIENTS      ||--o{ CLIENTS        : "refereix a"
    EMPLEATS     ||--o{ EMPLEATS       : "és cap de"

    CATEGORIES {
        int id PK
        varchar nom UK
        text descripcio
    }
    PROVEIDORS {
        int id PK
        varchar nom
        varchar pais
        varchar email
        boolean actiu
    }
    PRODUCTES {
        int id PK
        varchar nom
        int categoria_id FK
        int proveidor_id FK
        numeric preu
        numeric cost
        int stock
        boolean actiu
        date data_alta
    }
    CLIENTS {
        int id PK
        varchar nom
        varchar cognoms
        varchar email UK
        varchar ciutat
        varchar pais
        date data_registre
        int referit_per_id FK
    }
    EMPLEATS {
        int id PK
        varchar nom
        varchar cognoms
        varchar carrec
        int cap_id FK
        numeric salari
        date data_contractacio
        varchar ciutat
    }
    COMANDES {
        int id PK
        int client_id FK
        int empleat_id FK
        date data_comanda
        varchar estat
        varchar metode_pagament
        numeric despeses_enviament
    }
    LINIES_COMANDA {
        int id PK
        int comanda_id FK
        int producte_id FK
        int quantitat
        numeric preu_unitari
        numeric descompte
    }
    RESSENYES {
        int id PK
        int producte_id FK
        int client_id FK
        smallint puntuacio
        text comentari
        date data
    }
    DEVOLUCIONS {
        int id PK
        int comanda_id FK
        varchar motiu
        date data
        numeric import
    }

Hi reconeixeràs tot el de la lliçó anterior: nou relacions 1:N, una N:M resolta amb la taula pont linies_comanda, i dues relacions reflexives (clients.referit_per_id i empleats.cap_id).

  1. Les nou taules, una a una

3.1. categories — classificació del catàleg

Columna Tipus Nul Descripció
id INTEGER identity No PK. Identificador de la categoria
nom VARCHAR(60) No Nom visible. UNIQUE: no hi pot haver dues categories iguals
descripcio TEXT Text descriptiu per a la pàgina de categoria

6 files. Valors de nom: Alimentació, Cosmètica natural, Llar sostenible, Begudes, Higiene personal, Complements.

3.2. proveidors — qui subministra els productes

Columna Tipus Nul Descripció
id INTEGER identity No PK
nom VARCHAR(120) No Raó social del proveïdor
pais VARCHAR(60) No País d'origen: Espanya, Portugal, França, Alemanya
email VARCHAR(120) Contacte comercial
actiu BOOLEAN No FALSE si ja no se li compra. Per defecte TRUE

5 files. El proveïdor 5 (EcoNordic Supplies) està inactiu però conserva productes al catàleg: útil per practicar filtres i JOIN.

3.3. productes — el catàleg

Columna Tipus Nul Descripció
id INTEGER identity No PK
nom VARCHAR(150) No Nom comercial amb format/gramatge
categoria_id INTEGER FK → categories.id. ON DELETE RESTRICT
proveidor_id INTEGER FK → proveidors.id. ON DELETE RESTRICT
preu NUMERIC(10,2) No Preu de venda al públic, en euros
cost NUMERIC(10,2) Cost de compra. preu - cost és el marge brut
stock INTEGER No Unitats disponibles. Per defecte 0
actiu BOOLEAN No FALSE si està descatalogat (esborrat lògic)
data_alta DATE No Data d'incorporació al catàleg

20 files. Preus d'1,95 € a 22,00 €. Punts que necessitaràs en mòduls posteriors:

  • El producte 13 (Espelmes de cera de soja) té stock 0.
  • El producte 20 (Càpsules d'espirulina) té actiu = FALSE.
  • Els productes 13, 19 i 20 no s'han venut mai: no apareixen a linies_comanda. Són imprescindibles per a les lliçons de LEFT JOIN (03-03) i de nuls (04-03).

3.4. clients — qui compra

Columna Tipus Nul Descripció
id INTEGER identity No PK
nom VARCHAR(60) No Nom de pila
cognoms VARCHAR(90) No Cognoms
email VARCHAR(120) No UNIQUE. Clau natural del client
ciutat VARCHAR(80) Ciutat de residència
pais VARCHAR(60) No Espanya, Portugal o França
data_registre DATE No Alta a la botiga
referit_per_id INTEGER FK → clients.id (reflexiva). NULL = va arribar pel seu compte. ON DELETE SET NULL

15 files. 11 d'Espanya, 2 de Portugal, 2 de França. Vuit clients tenen referit_per_id. Els clients 13, 14 i 15 no han fet cap comanda: són necessaris per a les lliçons de LEFT JOIN i NOT EXISTS.

3.5. empleats — l'equip

Columna Tipus Nul Descripció
id INTEGER identity No PK
nom VARCHAR(60) No Nom de pila
cognoms VARCHAR(90) No Cognoms
carrec VARCHAR(80) No Càrrec
cap_id INTEGER FK → empleats.id (reflexiva). NULL només a direcció general. ON DELETE SET NULL
salari NUMERIC(10,2) Salari brut anual en euros
data_contractacio DATE No Data d'incorporació
ciutat VARCHAR(80) Ciutat de treball

8 files amb aquesta jerarquia:

Rosa Alcázar Vives (1) — Directora general
├── Andrés Company Talens (2) — Responsable de vendes
│   ├── Óscar Peris Blasco (4) — Comercial
│   ├── Laia Puig Sanchis (5) — Comercial
│   └── Marc Estévez Roig (6) — Atenció al client
├── Beatriz Nadal Ripoll (3) — Responsable de logística
│   └── Irene Salvador Mira (7) — Operària de magatzem
└── Daniel Vercher Lluch (8) — Analista de dades

Només els empleats 4, 5 i 6 tenen comandes assignades. Els altres cinc no apareixen mai a comandes.

3.6. comandes — la capçalera de cada compra

Columna Tipus Nul Descripció
id INTEGER identity No PK
client_id INTEGER No FK → clients.id. ON DELETE RESTRICT
empleat_id INTEGER FK → empleats.id. NULL = comanda web sense comercial. ON DELETE SET NULL
data_comanda DATE No Data en què es va fer
estat VARCHAR(20) No pendent, pagat, enviat, lliurat, cancellat
metode_pagament VARCHAR(20) No targeta, transferencia, paypal, contrareemborsament
despeses_enviament NUMERIC(10,2) No Ports cobrats. 0.00 quan l'enviament va ser gratuït

20 files repartides entre el març del 2025 i el febrer del 2026. Distribució per estat: 14 lliurat, 2 enviat, 2 pagat, 1 pendent, 1 cancellat. Deu comandes no tenen empleat assignat (empleat_id IS NULL).

Tant estat com metode_pagament estan protegits amb una restricció CHECK que impedeix valors fora del domini.

3.7. linies_comanda — el detall de cada compra

Columna Tipus Nul Descripció
id INTEGER identity No PK
comanda_id INTEGER No FK → comandes.id. ON DELETE CASCADE
producte_id INTEGER No FK → productes.id. ON DELETE RESTRICT
quantitat INTEGER No Unitats. Sempre > 0
preu_unitari NUMERIC(10,2) No Preu en el moment de la venda, no l'actual
descompte NUMERIC(4,2) No Fracció entre 0.00 i 1.00 (0.10 = 10 %). Per defecte 0.00

47 files. És la taula pont de la relació N:M entre comandes i productes, i la taula més consultada del curs: l'import d'una línia es calcula com quantitat * preu_unitari * (1 - descompte).

3.8. ressenyes — opinions de clients

Columna Tipus Nul Descripció
id INTEGER identity No PK
producte_id INTEGER No FK → productes.id. ON DELETE CASCADE
client_id INTEGER No FK → clients.id. ON DELETE CASCADE
puntuacio SMALLINT No D'1 a 5, protegit amb CHECK
comentari TEXT Text lliure de la ressenya
data DATE No Data de publicació

12 files. Totes corresponen a clients que van comprar aquest producte, i la seva data és sempre posterior a la de la comanda. La majoria dels productes no tenen cap ressenya, cosa que fa la taula ideal per practicar LEFT JOIN i HAVING.

3.9. devolucions — reemborsaments

Columna Tipus Nul Descripció
id INTEGER identity No PK
comanda_id INTEGER No FK → comandes.id. ON DELETE CASCADE
motiu VARCHAR(200) No Raó de la devolució
data DATE No Data de tramitació
import NUMERIC(10,2) No Quantitat reemborsada en euros

3 files, associades a les comandes 6, 10 i 13.

  1. Decisions de disseny que convé conèixer

Algunes eleccions de l'esquema no són evidents i les veuràs aparèixer una vegada i una altra als exercicis:

Decisió Motiu
Les columnes s'escriuen descripcio, puntuacio i linies_comanda, sense accents ni punt volat Els identificadors han de ser portables i ASCII. Les dades sí que porten accents i ç; els noms d'objecte, no
preu_unitari duplica informació de productes.preu És desnormalització deliberada: desa el preu històric. Dues línies antigues (comandes 1 i 2) tenen un preu menor que l'actual, precisament perquè ho puguis comprovar
descompte és una fracció (0.10), no un percentatge (10) Evita multiplicar i dividir per 100 a cada consulta: l'import és quantitat * preu_unitari * (1 - descompte)
No hi ha columna total a comandes Es calcula sumant les línies. Desar-lo seria un agregat precalculat que caldria mantenir sincronitzat (mòdul 10)
Tots els imports són NUMERIC(10,2) Diners exactes, mai coma flotant (lliçó 01-04)
Totes les dates són DATE Són dates de calendari, no instants amb zona horària
Totes les PK són id subrogat Uniformitat i estabilitat (lliçó 01-05)
Hi ha buits deliberats a les dades Clients sense comandes, productes sense vendre, comandes sense empleat, productes sense ressenyes: el curs els necessita

  1. L'script de creació de taules

Crea un fitxer anomenat botigaverda.sql i copia-hi els dos blocs d'aquesta secció i la següent, per ordre.

El primer bloc esborra les taules si ja existeixen (perquè puguis tornar a executar l'script tantes vegades com vulguis) i les crea en ordre de dependències: una taula no pot referenciar-ne una altra que encara no existeixi.

-- =====================================================================
-- BotigaVerda - Base de dades del curs de SQL
-- PostgreSQL 16
-- Bloc 1: creació de l'esquema
-- =====================================================================

-- Esborrat previ, en ordre invers a les dependències.
-- CASCADE elimina també les restriccions que apunten a aquestes taules.
DROP TABLE IF EXISTS devolucions    CASCADE;
DROP TABLE IF EXISTS ressenyes      CASCADE;
DROP TABLE IF EXISTS linies_comanda CASCADE;
DROP TABLE IF EXISTS comandes       CASCADE;
DROP TABLE IF EXISTS empleats       CASCADE;
DROP TABLE IF EXISTS clients        CASCADE;
DROP TABLE IF EXISTS productes      CASCADE;
DROP TABLE IF EXISTS proveidors     CASCADE;
DROP TABLE IF EXISTS categories     CASCADE;

-- ---------------------------------------------------------------------
-- 1. Taules sense dependències
-- ---------------------------------------------------------------------
CREATE TABLE categories (
    id          INTEGER      GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    nom         VARCHAR(60)  NOT NULL UNIQUE,
    descripcio  TEXT
);

CREATE TABLE proveidors (
    id     INTEGER      GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    nom    VARCHAR(120) NOT NULL,
    pais   VARCHAR(60)  NOT NULL,
    email  VARCHAR(120),
    actiu  BOOLEAN      NOT NULL DEFAULT TRUE
);

-- ---------------------------------------------------------------------
-- 2. Taules que depenen de les anteriors
-- ---------------------------------------------------------------------
CREATE TABLE productes (
    id           INTEGER       GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    nom          VARCHAR(150)  NOT NULL,
    categoria_id INTEGER       REFERENCES categories(id) ON DELETE RESTRICT,
    proveidor_id INTEGER       REFERENCES proveidors(id) ON DELETE RESTRICT,
    preu         NUMERIC(10,2) NOT NULL CHECK (preu >= 0),
    cost         NUMERIC(10,2) CHECK (cost >= 0),
    stock        INTEGER       NOT NULL DEFAULT 0 CHECK (stock >= 0),
    actiu        BOOLEAN       NOT NULL DEFAULT TRUE,
    data_alta    DATE          NOT NULL DEFAULT CURRENT_DATE
);

-- Relació reflexiva: un client pot haver estat referit per un altre client
CREATE TABLE clients (
    id             INTEGER      GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    nom            VARCHAR(60)  NOT NULL,
    cognoms        VARCHAR(90)  NOT NULL,
    email          VARCHAR(120) NOT NULL UNIQUE,
    ciutat         VARCHAR(80),
    pais           VARCHAR(60)  NOT NULL,
    data_registre  DATE         NOT NULL DEFAULT CURRENT_DATE,
    referit_per_id INTEGER      REFERENCES clients(id) ON DELETE SET NULL
);

-- Relació reflexiva: jerarquia organitzativa
CREATE TABLE empleats (
    id                 INTEGER       GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    nom                VARCHAR(60)   NOT NULL,
    cognoms            VARCHAR(90)   NOT NULL,
    carrec             VARCHAR(80)   NOT NULL,
    cap_id             INTEGER       REFERENCES empleats(id) ON DELETE SET NULL,
    salari             NUMERIC(10,2) CHECK (salari >= 0),
    data_contractacio  DATE          NOT NULL,
    ciutat             VARCHAR(80)
);

CREATE TABLE comandes (
    id                 INTEGER       GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    client_id          INTEGER       NOT NULL REFERENCES clients(id)  ON DELETE RESTRICT,
    empleat_id         INTEGER                REFERENCES empleats(id) ON DELETE SET NULL,
    data_comanda       DATE          NOT NULL,
    estat              VARCHAR(20)   NOT NULL
                       CHECK (estat IN ('pendent','pagat','enviat','lliurat','cancellat')),
    metode_pagament    VARCHAR(20)   NOT NULL
                       CHECK (metode_pagament IN ('targeta','transferencia','paypal','contrareemborsament')),
    despeses_enviament NUMERIC(10,2) NOT NULL DEFAULT 0 CHECK (despeses_enviament >= 0)
);

-- ---------------------------------------------------------------------
-- 3. Taula pont de la relació N:M entre comandes i productes
-- ---------------------------------------------------------------------
CREATE TABLE linies_comanda (
    id            INTEGER       GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    comanda_id    INTEGER       NOT NULL REFERENCES comandes(id)  ON DELETE CASCADE,
    producte_id   INTEGER       NOT NULL REFERENCES productes(id) ON DELETE RESTRICT,
    quantitat     INTEGER       NOT NULL CHECK (quantitat > 0),
    preu_unitari  NUMERIC(10,2) NOT NULL CHECK (preu_unitari >= 0),
    descompte     NUMERIC(4,2)  NOT NULL DEFAULT 0
                  CHECK (descompte >= 0 AND descompte <= 1)
);

-- ---------------------------------------------------------------------
-- 4. Taules satèl·lit
-- ---------------------------------------------------------------------
CREATE TABLE ressenyes (
    id          INTEGER  GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    producte_id INTEGER  NOT NULL REFERENCES productes(id) ON DELETE CASCADE,
    client_id   INTEGER  NOT NULL REFERENCES clients(id)   ON DELETE CASCADE,
    puntuacio   SMALLINT NOT NULL CHECK (puntuacio BETWEEN 1 AND 5),
    comentari   TEXT,
    data        DATE     NOT NULL
);

CREATE TABLE devolucions (
    id         INTEGER       GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    comanda_id INTEGER       NOT NULL REFERENCES comandes(id) ON DELETE CASCADE,
    motiu      VARCHAR(200)  NOT NULL,
    data       DATE          NOT NULL,
    import     NUMERIC(10,2) NOT NULL CHECK (import >= 0)
);

Què fa cada part, en dos minuts

Sense entrar en la sintaxi (mòdul 5), aquests són els elements que veuràs repetir-se:

Element Què significa
GENERATED BY DEFAULT AS IDENTITY L'id es genera sol si no l'indiques. És la forma estàndard moderna d'un autoincremental
PRIMARY KEY Clau primària: única i no nul·la
NOT NULL La columna és obligatòria
UNIQUE No hi pot haver dues files amb el mateix valor (categories.nom, clients.email)
REFERENCES taula(id) Clau forana: el valor ha d'existir a la taula referenciada
ON DELETE RESTRICT / CASCADE / SET NULL Què fer si s'esborra el pare (lliçó 01-05)
CHECK (...) Restricció de domini: rebutja valors fora del rang permès
DEFAULT valor Valor que es fa servir si no n'indiques cap

Fixa't en dos detalls de l'ordre:

  1. Les taules es creen en ordre de dependències. productes referencia categories i proveidors, així que aquestes dues van abans. L'esborrat (DROP) es fa en l'ordre invers.
  2. Les taules reflexives es referencien a si mateixes dins de la seva pròpia definició (clients.referit_per_id REFERENCES clients(id)). PostgreSQL ho permet sense cap problema.

  1. L'script de càrrega de dades

Afegeix aquest segon bloc al final del mateix fitxer botigaverda.sql.

-- =====================================================================
-- Bloc 2: càrrega de dades
-- =====================================================================

-- ---------------------------------------------------------------------
-- categories (6)
-- ---------------------------------------------------------------------
INSERT INTO categories (id, nom, descripcio) VALUES
(1, 'Alimentació',       'Productes ecològics d''alimentació seca i conserves'),
(2, 'Cosmètica natural', 'Cosmètica amb ingredients naturals i sense parabens'),
(3, 'Llar sostenible',   'Neteja i parament amb materials reutilitzables'),
(4, 'Begudes',           'Infusions, sucs i fermentats ecològics'),
(5, 'Higiene personal',  'Higiene diària amb envasos reduïts o compostables'),
(6, 'Complements',       'Suplements alimentaris d''origen vegetal');

-- ---------------------------------------------------------------------
-- proveidors (5) - el 5 està inactiu però conserva productes
-- ---------------------------------------------------------------------
INSERT INTO proveidors (id, nom, pais, email, actiu) VALUES
(1, 'Huerta del Turia',   'Espanya',  '[email protected]',    TRUE),
(2, 'BioSierra Ibérica',  'Espanya',  '[email protected]',       TRUE),
(3, 'Verde Atlántico',    'Portugal', '[email protected]', TRUE),
(4, 'Maison Nature',      'França',   '[email protected]',      TRUE),
(5, 'EcoNordic Supplies', 'Alemanya', '[email protected]',           FALSE);

-- ---------------------------------------------------------------------
-- productes (20)
--   13 -> stock 0 ; 20 -> actiu FALSE ; 13, 19 i 20 mai venuts
-- ---------------------------------------------------------------------
INSERT INTO productes (id, nom, categoria_id, proveidor_id, preu, cost, stock, actiu, data_alta) VALUES
( 1, 'Oli d''oliva verge extra 500 ml',         1, 1, 12.50,  7.80, 120, TRUE,  '2025-01-15'),
( 2, 'Arròs integral ecològic 1 kg',            1, 1,  3.90,  2.10, 200, TRUE,  '2025-01-15'),
( 3, 'Mel de tarongina crua 500 g',             1, 2,  9.75,  5.40,  80, TRUE,  '2025-01-15'),
( 4, 'Pasta d''espelta 500 g',                  1, 2,  2.80,  1.35, 150, TRUE,  '2025-01-20'),
( 5, 'Tomàquet triturat ecològic 400 g',        1, 1,  1.95,  0.90, 300, TRUE,  '2025-01-20'),
( 6, 'Crema facial d''àloe vera 50 ml',         2, 4, 18.90,  9.50,  60, TRUE,  '2025-01-20'),
( 7, 'Xampú sòlid de romaní 80 g',              2, 4,  8.40,  3.60,  95, TRUE,  '2025-02-01'),
( 8, 'Oli corporal d''ametlles 200 ml',         2, 3, 14.25,  7.10,  45, TRUE,  '2025-02-01'),
( 9, 'Bàlsam labial de calèndula 15 ml',        2, 4,  4.60,  1.80, 130, TRUE,  '2025-02-01'),
(10, 'Detergent ecològic concentrat 1 L',       3, 5, 11.20,  6.00,  70, TRUE,  '2025-02-10'),
(11, 'Fregall vegetal de lufa (pack 3)',        3, 3,  5.50,  2.20, 110, TRUE,  '2025-02-10'),
(12, 'Bosses reutilitzables de cotó (pack 5)',  3, 3,  9.90,  4.30,  85, TRUE,  '2025-02-10'),
(13, 'Espelmes de cera de soja (pack 2)',       3, 5, 13.75,  6.90,   0, TRUE,  '2025-03-01'),
(14, 'Infusió de camamilla ecològica 20 u',     4, 2,  3.25,  1.40, 180, TRUE,  '2025-02-20'),
(15, 'Te verd matcha cerimonial 30 g',          4, 3, 22.00, 12.50,  40, TRUE,  '2025-02-20'),
(16, 'Kombutxa de gingebre 750 ml',             4, 1,  4.95,  2.30,  60, TRUE,  '2025-03-15'),
(17, 'Suc de taronja premsat en fred 1 L',      4, 1,  5.40,  2.60,  90, TRUE,  '2025-03-15'),
(18, 'Raspall de dents de bambú',               5, 5,  3.50,  1.20, 240, TRUE,  '2025-04-01'),
(19, 'Desodorant natural en barra 50 g',        5, 4,  7.80,  3.30,  75, TRUE,  '2025-05-10'),
(20, 'Càpsules d''espirulina 120 u',            6, 5, 16.40,  8.70,  55, FALSE, '2025-06-01');

-- ---------------------------------------------------------------------
-- clients (15) - els 13, 14 i 15 no han fet cap comanda
-- ---------------------------------------------------------------------
INSERT INTO clients (id, nom, cognoms, email, ciutat, pais, data_registre, referit_per_id) VALUES
( 1, 'Lucía',   'Martínez Soler',  '[email protected]',  'València',  'Espanya',  '2025-01-10', NULL),
( 2, 'Carlos',  'Ferrer Ibáñez',   '[email protected]',   'València',  'Espanya',  '2025-01-22', 1),
( 3, 'Marta',   'Sanchis Gil',     '[email protected]',   'Castelló',  'Espanya',  '2025-02-03', 1),
( 4, 'Javier',  'Ortega Ruiz',     '[email protected]',   'Madrid',    'Espanya',  '2025-02-14', NULL),
( 5, 'Ana',     'Belmonte Roca',   '[email protected]',    'Barcelona', 'Espanya',  '2025-02-27', 2),
( 6, 'Pau',     'Llorens Vidal',   '[email protected]',     'València',  'Espanya',  '2025-03-09', NULL),
( 7, 'Sofia',   'Moreira Costa',   '[email protected]',    'Lisboa',    'Portugal', '2025-03-21', NULL),
( 8, 'Tiago',   'Almeida Nunes',   '[email protected]',    'Porto',     'Portugal', '2025-04-04', 7),
( 9, 'Camille', 'Dubois',          '[email protected]',   'Lió',       'França',   '2025-04-18', NULL),
(10, 'Julien',  'Moreau',          '[email protected]',    'París',     'França',   '2025-05-02', 9),
(11, 'Elena',   'Navarro Puig',    '[email protected]',   'Alacant',   'Espanya',  '2025-05-16', 6),
(12, 'Diego',   'Ramos Herrera',   '[email protected]',     'Sevilla',   'Espanya',  '2025-06-01', NULL),
(13, 'Núria',   'Bosch Ferrer',    '[email protected]',     'Barcelona', 'Espanya',  '2025-06-20', 5),
(14, 'Hugo',    'Iglesias Pardo',  '[email protected]',   'Saragossa', 'Espanya',  '2025-09-12', NULL),
(15, 'Inés',    'Carrasco Vega',   '[email protected]',   'València',  'Espanya',  '2026-01-08', 1);

-- ---------------------------------------------------------------------
-- empleats (8) - jerarquia via cap_id ; només 4, 5 i 6 gestionen comandes
-- ---------------------------------------------------------------------
INSERT INTO empleats (id, nom, cognoms, carrec, cap_id, salari, data_contractacio, ciutat) VALUES
(1, 'Rosa',    'Alcázar Vives',   'Directora general',        NULL, 62000.00, '2024-09-01', 'València'),
(2, 'Andrés',  'Company Talens',  'Responsable de vendes',       1, 41000.00, '2024-10-15', 'València'),
(3, 'Beatriz', 'Nadal Ripoll',    'Responsable de logística',    1, 39500.00, '2024-11-02', 'València'),
(4, 'Óscar',   'Peris Blasco',    'Comercial',                   2, 28500.00, '2025-01-13', 'València'),
(5, 'Laia',    'Puig Sanchis',    'Comercial',                   2, 27800.00, '2025-02-17', 'Castelló'),
(6, 'Marc',    'Estévez Roig',    'Atenció al client',           2, 24500.00, '2025-03-24', 'València'),
(7, 'Irene',   'Salvador Mira',   'Operària de magatzem',        3, 22000.00, '2025-04-07', 'València'),
(8, 'Daniel',  'Vercher Lluch',   'Analista de dades',           1, 35000.00, '2025-06-16', 'València');

-- ---------------------------------------------------------------------
-- comandes (20) - 10 sense empleat assignat (comandes web)
-- ---------------------------------------------------------------------
INSERT INTO comandes (id, client_id, empleat_id, data_comanda, estat, metode_pagament, despeses_enviament) VALUES
( 1,  1, NULL, '2025-03-04', 'lliurat',   'targeta',              4.95),
( 2,  2,    4, '2025-03-12', 'lliurat',   'transferencia',        0.00),
( 3,  3, NULL, '2025-04-02', 'lliurat',   'targeta',              4.95),
( 4,  4,    5, '2025-04-19', 'lliurat',   'paypal',               4.95),
( 5,  1, NULL, '2025-05-07', 'lliurat',   'targeta',              0.00),
( 6,  5,    4, '2025-05-23', 'cancellat', 'targeta',              4.95),
( 7,  6, NULL, '2025-06-11', 'lliurat',   'contrareemborsament',  6.50),
( 8,  7,    5, '2025-06-28', 'lliurat',   'targeta',              9.90),
( 9,  8, NULL, '2025-07-15', 'lliurat',   'paypal',               9.90),
(10,  9,    4, '2025-08-03', 'lliurat',   'targeta',             12.50),
(11,  2, NULL, '2025-09-09', 'lliurat',   'targeta',              0.00),
(12, 10,    5, '2025-10-01', 'lliurat',   'transferencia',       12.50),
(13, 11, NULL, '2025-10-22', 'lliurat',   'targeta',              4.95),
(14, 12,    6, '2025-11-14', 'lliurat',   'paypal',               4.95),
(15,  1, NULL, '2025-12-02', 'lliurat',   'targeta',              0.00),
(16,  4,    4, '2025-12-19', 'enviat',    'targeta',              4.95),
(17,  7, NULL, '2026-01-13', 'enviat',    'paypal',               9.90),
(18,  5,    5, '2026-01-27', 'pagat',     'transferencia',        4.95),
(19,  6, NULL, '2026-02-09', 'pagat',     'targeta',              4.95),
(20,  9,    6, '2026-02-21', 'pendent',   'contrareemborsament', 12.50);

-- ---------------------------------------------------------------------
-- linies_comanda (47)
--   Les línies 1 i 4 porten el preu HISTÒRIC, anterior a la pujada
--   de tarifes de l'abril del 2025: per això no coincideix amb productes.preu
-- ---------------------------------------------------------------------
INSERT INTO linies_comanda (id, comanda_id, producte_id, quantitat, preu_unitari, descompte) VALUES
( 1,  1,  1, 2, 11.95, 0.00),
( 2,  1,  2, 3,  3.90, 0.00),
( 3,  1, 14, 2,  3.25, 0.00),
( 4,  2,  6, 1, 17.50, 0.00),
( 5,  2,  9, 2,  4.60, 0.00),
( 6,  3,  5, 6,  1.95, 0.10),
( 7,  3,  4, 4,  2.80, 0.00),
( 8,  3,  2, 2,  3.90, 0.00),
( 9,  4, 15, 1, 22.00, 0.00),
(10,  4,  3, 1,  9.75, 0.00),
(11,  5, 10, 1, 11.20, 0.00),
(12,  5, 11, 2,  5.50, 0.00),
(13,  5, 12, 1,  9.90, 0.00),
(14,  6,  1, 1, 12.50, 0.00),
(15,  6,  8, 1, 14.25, 0.00),
(16,  7, 16, 4,  4.95, 0.00),
(17,  7, 17, 2,  5.40, 0.00),
(18,  8,  1, 3, 12.50, 0.05),
(19,  8,  3, 2,  9.75, 0.00),
(20,  8, 14, 3,  3.25, 0.00),
(21,  9,  7, 2,  8.40, 0.00),
(22,  9,  9, 3,  4.60, 0.00),
(23,  9, 18, 4,  3.50, 0.00),
(24, 10,  6, 2, 18.90, 0.10),
(25, 10,  8, 1, 14.25, 0.00),
(26, 11,  2, 5,  3.90, 0.00),
(27, 11,  5, 8,  1.95, 0.15),
(28, 12, 15, 2, 22.00, 0.00),
(29, 12, 14, 4,  3.25, 0.00),
(30, 12, 16, 2,  4.95, 0.00),
(31, 13, 12, 2,  9.90, 0.00),
(32, 13, 18, 3,  3.50, 0.00),
(33, 14,  1, 1, 12.50, 0.00),
(34, 14,  4, 3,  2.80, 0.00),
(35, 14, 17, 2,  5.40, 0.00),
(36, 15,  3, 2,  9.75, 0.00),
(37, 15,  7, 1,  8.40, 0.00),
(38, 15, 11, 1,  5.50, 0.00),
(39, 16, 10, 2, 11.20, 0.05),
(40, 16, 12, 1,  9.90, 0.00),
(41, 17,  1, 2, 12.50, 0.00),
(42, 17, 15, 1, 22.00, 0.00),
(43, 18,  6, 1, 18.90, 0.00),
(44, 18,  9, 2,  4.60, 0.00),
(45, 19, 16, 6,  4.95, 0.10),
(46, 20,  2, 4,  3.90, 0.00),
(47, 20, 18, 2,  3.50, 0.00);

-- ---------------------------------------------------------------------
-- ressenyes (12) - sempre de clients que van comprar aquest producte
-- ---------------------------------------------------------------------
INSERT INTO ressenyes (id, producte_id, client_id, puntuacio, comentari, data) VALUES
( 1,  1,  1, 5, 'Oli excel·lent, gust intens i envàs molt cuidat.',            '2025-03-15'),
( 2,  2,  1, 4, 'Bon arròs, tot i que triga una mica més a bullir del normal.', '2025-03-16'),
( 3,  6,  2, 5, 'La crema deixa la pell molt suau. Repetiré sens dubte.',      '2025-03-25'),
( 4,  5,  3, 3, 'Correcte pel preu, res espectacular.',                        '2025-04-12'),
( 5, 15,  4, 5, 'Matcha de qualitat cerimonial de debò, color impecable.',     '2025-05-02'),
( 6, 16,  6, 2, 'Massa gingebre per al meu gust, gairebé no es pot beure.',    '2025-06-20'),
( 7,  1,  7, 5, 'El compro cada mes, insuperable relació qualitat-preu.',      '2025-07-08'),
( 8, 18,  8, 4, 'Compleix perfectament, tot i que les cerres són dures.',      '2025-07-26'),
( 9,  6,  9, 4, 'Molt bona hidratació, l''enviament a França va trigar molt.', '2025-08-14'),
(10,  2,  2, 5, 'Gra solt i gust net, millor que el del supermercat.',         '2025-09-19'),
(11, 12, 11, 3, 'Bosses resistents però més petites del que esperava.',        '2025-11-03'),
(12, 10,  4, 4, 'Ret moltíssim, un litre dura mesos.',                         '2026-01-10');

-- ---------------------------------------------------------------------
-- devolucions (3)
-- ---------------------------------------------------------------------
INSERT INTO devolucions (id, comanda_id, motiu, data, import) VALUES
(1,  6, 'Comanda cancel·lada pel client abans de l''enviament', '2025-05-25', 26.75),
(2, 10, 'Producte malmès durant el transport',                  '2025-08-11', 34.02),
(3, 13, 'El format no correspon al que s''esperava',            '2025-10-30', 19.80);

-- ---------------------------------------------------------------------
-- Sincronitzar les seqüències d'identitat amb els ids ja inserits,
-- perquè els futurs INSERT sense id no xoquin amb les claus utilitzades.
-- ---------------------------------------------------------------------
SELECT setval(pg_get_serial_sequence('categories',     'id'), (SELECT MAX(id) FROM categories));
SELECT setval(pg_get_serial_sequence('proveidors',     'id'), (SELECT MAX(id) FROM proveidors));
SELECT setval(pg_get_serial_sequence('productes',      'id'), (SELECT MAX(id) FROM productes));
SELECT setval(pg_get_serial_sequence('clients',        'id'), (SELECT MAX(id) FROM clients));
SELECT setval(pg_get_serial_sequence('empleats',       'id'), (SELECT MAX(id) FROM empleats));
SELECT setval(pg_get_serial_sequence('comandes',       'id'), (SELECT MAX(id) FROM comandes));
SELECT setval(pg_get_serial_sequence('linies_comanda', 'id'), (SELECT MAX(id) FROM linies_comanda));
SELECT setval(pg_get_serial_sequence('ressenyes',      'id'), (SELECT MAX(id) FROM ressenyes));
SELECT setval(pg_get_serial_sequence('devolucions',    'id'), (SELECT MAX(id) FROM devolucions));

Explicació de l'script de càrrega per blocs

Bloc Què carrega Detall que importa
categories 6 categories Cap dependència: van primer
proveidors 5 proveïdors El 5 (EcoNordic) té actiu = FALSE però manté productes
productes 20 productes El 13 amb stock 0, el 20 descatalogat; 13, 19 i 20 no es vendran mai
clients 15 clients Vuit amb referit_per_id; els 13, 14 i 15 sense comandes
empleats 8 empleats La 1 té cap_id NULL; només 4, 5 i 6 apareixen a comandes
comandes 20 comandes Març 2025 – febrer 2026; 10 amb empleat_id NULL
linies_comanda 47 línies Dues amb preu històric diferent de l'actual
ressenyes 12 ressenyes Data sempre posterior a la de la comanda corresponent
devolucions 3 devolucions Associades a les comandes 6, 10 i 13
setval(...) Ajusta les seqüències després d'inserir ids explícits

Aquest últim bloc mereix una explicació. Com que hem inserit els id a mà, el comptador intern de cada taula continua a 1. Si demà inserissis un client sense indicar id, PostgreSQL intentaria assignar-li l'1 i fallaria per clau duplicada. setval avança el comptador fins al màxim ja utilitzat. És un detall habitual en carregar dades inicials, i el veuràs de nou al mòdul 5.

Nota sobre els INSERT múltiples: cada sentència insereix moltes files amb una sola instrucció, separant les tuples per comes. És molt més ràpid que un INSERT per fila, i la sintaxi completa s'estudia a la lliçó 05-02.

  1. Com carregar la base de dades

Si encara no l'has creada, repassa la lliçó 01-02. Amb la base botigaverda ja existent, hi ha dues maneres d'executar l'script.

Des de la línia d'ordres (recomanada)

psql -h localhost -U curs_sql -d botigaverda -f botigaverda.sql

Sortida esperada (resumida):

DROP TABLE
...
CREATE TABLE
CREATE TABLE
...
INSERT 0 6
INSERT 0 5
INSERT 0 20
INSERT 0 15
INSERT 0 8
INSERT 0 20
INSERT 0 47
INSERT 0 12
INSERT 0 3
 setval
--------
      6
...

Cada INSERT 0 N et diu quantes files ha inserit aquella sentència. Si veus els números 6, 5, 20, 15, 8, 20, 47, 12 i 3 en aquest ordre, la càrrega ha anat bé.

Des de dins de psql

psql -h localhost -U curs_sql -d botigaverda
botigaverda=> \i botigaverda.sql

Amb Docker, si el fitxer és a la teva màquina i PostgreSQL al contenidor, el pots copiar a dins o canalitzar-lo directament:

docker exec -i pg-curs psql -U postgres -d botigaverda < botigaverda.sql

Si alguna cosa falla

Error Causa i solució
permission denied for schema public Falta GRANT ALL ON SCHEMA public TO curs_sql; executat com a superusuari
database "botigaverda" does not exist Crea-la primer (lliçó 01-02)
Caràcters estranys en lloc d'accents La base no està en UTF-8. Recrea-la amb ENCODING 'UTF8'
relation "categories" already exists Estàs executant només el bloc 2. Executa el fitxer complet: el DROP TABLE IF EXISTS inicial ho resol

L'script és idempotent: el pots executar tantes vegades com vulguis i sempre et deixarà la base en el mateix estat. Si en algun mòdul fas experiments amb UPDATE o DELETE i vols tornar al punt de partida, n'hi ha prou amb tornar-lo a llançar.

  1. Consultes de verificació

Comprova que tot és on ha de ser.

8.1. Les nou taules existeixen

botigaverda=> \dt

Has de veure les nou: categories, clients, comandes, devolucions, empleats, linies_comanda, productes, proveidors, ressenyes.

8.2. Nombre de files per taula

SELECT 'categories'     AS taula, COUNT(*) AS files FROM categories
UNION ALL SELECT 'proveidors',     COUNT(*) FROM proveidors
UNION ALL SELECT 'productes',      COUNT(*) FROM productes
UNION ALL SELECT 'clients',        COUNT(*) FROM clients
UNION ALL SELECT 'empleats',       COUNT(*) FROM empleats
UNION ALL SELECT 'comandes',       COUNT(*) FROM comandes
UNION ALL SELECT 'linies_comanda', COUNT(*) FROM linies_comanda
UNION ALL SELECT 'ressenyes',      COUNT(*) FROM ressenyes
UNION ALL SELECT 'devolucions',    COUNT(*) FROM devolucions;

Resultat esperat:

taula files
categories 6
proveidors 5
productes 20
clients 15
empleats 8
comandes 20
linies_comanda 47
ressenyes 12
devolucions 3

(No et preocupis per la sintaxi d'UNION ALL ni de COUNT: s'estudien als mòduls 3 i 4. Aquí és només una eina de verificació.)

8.3. Els "buits" deliberats són on han de ser

SELECT
  (SELECT COUNT(*) FROM clients   WHERE id NOT IN (SELECT client_id FROM comandes))            AS clients_sense_comandes,
  (SELECT COUNT(*) FROM productes WHERE id NOT IN (SELECT producte_id FROM linies_comanda))    AS productes_sense_vendre,
  (SELECT COUNT(*) FROM comandes  WHERE empleat_id IS NULL)                                    AS comandes_sense_empleat,
  (SELECT COUNT(*) FROM clients   WHERE referit_per_id IS NULL)                                AS clients_no_referits,
  (SELECT COUNT(*) FROM empleats  WHERE cap_id IS NULL)                                        AS empleats_sense_cap;
clients_sense_comandes productes_sense_vendre comandes_sense_empleat clients_no_referits empleats_sense_cap
3 3 10 7 1

Si aquests cinc números coincideixen, la teva base de dades és exactament la que faran servir totes les lliçons següents.

8.4. Un cop d'ull a les dades

SELECT id, nom, preu, stock, actiu FROM productes WHERE id <= 5;
id nom preu stock actiu
1 Oli d'oliva verge extra 500 ml 12.50 120 true
2 Arròs integral ecològic 1 kg 3.90 200 true
3 Mel de tarongina crua 500 g 9.75 80 true
4 Pasta d'espelta 500 g 2.80 150 true
5 Tomàquet triturat ecològic 400 g 1.95 300 true
SELECT estat, COUNT(*) AS comandes FROM comandes GROUP BY estat ORDER BY comandes DESC;
estat comandes
lliurat 14
enviat 2
pagat 2
cancellat 1
pendent 1

Errors habituals i consells

  • Executar els blocs desordenats. Les taules s'han de crear abans que les seves dependents i les dades s'han de carregar en el mateix ordre. Executa sempre el fitxer complet.
  • Copiar l'script a mitges. Un INSERT tallat per la meitat deixa la base incoherent. Copia bloc a bloc i comprova els comptadors de files.
  • Modificar les dades i no poder tornar enrere. L'script és idempotent: torna'l a llançar i tornes a l'estat inicial. Desa'l en un lloc localitzable.
  • Sorprendre's que preu_unitari no coincideixi amb preu. A les línies 1 i 4 és intencionat: són preus històrics anteriors a la pujada de tarifes.
  • Interpretar descompte com a percentatge. És una fracció: 0.10 significa 10 %. L'import d'una línia és quantitat * preu_unitari * (1 - descompte).
  • Esperar ressenyes de tots els productes. Només 9 dels 20 productes tenen ressenyes, i això és deliberat.
  • Oblidar el setval final. Sense ell, el primer INSERT sense id explícit fallarà per clau duplicada.
  • Consell: tingues el diagrama a mà. Torna a aquesta lliçó cada vegada que dubtis de quina taula conté quina columna; t'estalviarà molts column does not exist.
  • Consell: fes servir \d taula abans de cada exercici. És més ràpid que buscar al text.
  • Consell: crea una còpia de seguretat. pg_dump -U curs_sql -d botigaverda -f copia.sql et permet restaurar en segons si trenques alguna cosa.

Exercicis

Exercici 1

Carrega la base de dades al teu equip i verifica la instal·lació responent aquestes quatre preguntes amb les ordres i consultes de la secció 8:

  1. Existeixen les nou taules?
  2. Quantes files té cadascuna?
  3. Quines columnes, tipus i claus foranes té linies_comanda?
  4. Quantes comandes no tenen empleat assignat?

Exercici 2

Sense executar res, i fent servir només el diagrama i les descripcions de taules, digues quines taules i quines columnes necessitaries per respondre cada pregunta de negoci. No escriguis la consulta: indica el camí entre taules.

  1. Quina és la puntuació mitjana del producte "Oli d'oliva verge extra 500 ml"?
  2. Quin comercial va gestionar la comanda número 12 i qui és el seu cap?
  3. Quant va facturar la comanda 8, despeses d'enviament incloses?
  4. De quin país és el proveïdor del producte més car del catàleg?
  5. Quins clients van ser referits per Lucía Martínez Soler?

Exercici 3

Prediu què passarà amb cadascuna d'aquestes operacions sobre la base acabada de carregar. Després executa-les i comprova la teva predicció (recorda que pots recarregar l'script per tornar a l'estat inicial).

-- a)
INSERT INTO categories (nom, descripcio) VALUES ('Begudes', 'Duplicada');

-- b)
INSERT INTO ressenyes (producte_id, client_id, puntuacio, comentari, data)
VALUES (1, 14, 7, 'Genial', '2026-03-01');

-- c)
INSERT INTO linies_comanda (comanda_id, producte_id, quantitat, preu_unitari, descompte)
VALUES (20, 13, 1, 13.75, 0.00);

-- d)
DELETE FROM comandes WHERE id = 5;
SELECT COUNT(*) FROM linies_comanda;

Solucions

Solució 1

botigaverda=> \dt

Nou taules llistades. Per al recompte de files, la consulta amb UNION ALL de la secció 8.2, que ha de retornar 6, 5, 20, 15, 8, 20, 47, 12 i 3.

Per a l'estructura de linies_comanda:

botigaverda=> \d linies_comanda

Mostra les sis columnes (id, comanda_id, producte_id, quantitat, preu_unitari, descompte), els seus tipus, la PK sobre id i les dues claus foranes: comanda_id → comandes(id) ON DELETE CASCADE i producte_id → productes(id) ON DELETE RESTRICT.

I per a les comandes sense empleat:

SELECT COUNT(*) FROM comandes WHERE empleat_id IS NULL;
count
10

Solució 2

# Pregunta Camí entre taules Columnes clau
1 Puntuació mitjana d'un producte productesressenyes productes.nom, ressenyes.producte_id, ressenyes.puntuacio
2 Comercial de la comanda 12 i el seu cap comandesempleatsempleats (self join) comandes.empleat_id, empleats.id, empleats.cap_id
3 Facturació de la comanda 8 comandeslinies_comanda linies_comanda.quantitat, preu_unitari, descompte, més comandes.despeses_enviament
4 País del proveïdor del producte més car productesproveidors productes.preu, productes.proveidor_id, proveidors.pais
5 Clients referits per la Lucía clientsclients (self join) clients.id, clients.referit_per_id, clients.nom

Fixa't que dues de les cinc preguntes requereixen unir una taula amb si mateixa: és el que veurem com a SELF JOIN a la lliçó 03-06, i és la conseqüència directa de les relacions reflexives de l'esquema.

Solució 3

a) Falla per la restricció UNIQUE de categories.nom:

ERROR:  duplicate key value violates unique constraint "categories_nom_key"
DETAIL:  Key (nom)=(Begudes) already exists.

És la protecció de la clau natural que vam esmentar a la lliçó 01-05: encara que la PK sigui id, el UNIQUE impedeix duplicar el nom.

b) Falla per la restricció CHECK de la puntuació:

ERROR:  new row for relation "ressenyes" violates check constraint "ressenyes_puntuacio_check"
DETAIL:  Failing row contains (13, 1, 14, 7, Genial, 2026-03-01).

El domini de puntuacio és 1-5, i el tipus SMALLINT per si sol no ho garanteix: cal el CHECK. Observa a més que el client 14 no ha comprat mai aquest producte; la base de dades no ho impedeix, perquè cap restricció no ho exigeix. És un bon recordatori que les restriccions només protegeixen allò que declares explícitament.

c) Funciona. El producte 13 (Espelmes de cera de soja) existeix i la comanda 20 també, així que la FK se satisfà. Ara linies_comanda tindria 48 files i el producte 13 deixaria d'estar "sense vendre".

INSERT 0 1

Compte: això trenca un dels buits deliberats del conjunt de dades. Si ho executes, recarrega l'script abans de continuar amb el mòdul 3, o alguns resultats de les lliçons de LEFT JOIN no coincidiran. Fixa't a més que la base no ha comprovat si hi ha estoc (el producte 13 té 0 unitats): aquesta regla és lògica de negoci i no està declarada com a restricció.

d) Funciona, i esborra en cascada. La comanda 5 té tres línies (ids 11, 12 i 13), i linies_comanda.comanda_id està declarada ON DELETE CASCADE:

DELETE 1
count
44

Una sola sentència ha eliminat quatre files en dues taules. És exactament el comportament que vam anticipar a la lliçó 01-05 i la raó per la qual CASCADE s'ha de reservar per a relacions de composició real. Recarrega l'script per recuperar l'estat original.

Conclusió

Amb aquesta lliçó tanques el mòdul 1 i, sobretot, ja tens el terreny preparat:

  • Coneixes BotigaVerda com a negoci: què ven, a qui, per quins canals i amb quin equip.
  • Saps llegir el seu diagrama entitat-relació: nou taules, nou relacions 1:N, una N:M resolta amb linies_comanda i dues relacions reflexives a clients i empleats.
  • Tens la descripció columna a columna de les nou taules, amb els seus tipus, claus i significats.
  • Has executat l'script complet i verificat els recomptes: 6 categories, 5 proveïdors, 20 productes, 15 clients, 8 empleats, 20 comandes, 47 línies, 12 ressenyes i 3 devolucions.
  • Entens els buits deliberats del conjunt de dades —3 clients sense comandes, 3 productes sense vendre, 10 comandes sense empleat, 11 productes sense ressenyes— i per què les lliçons de LEFT JOIN, NULL i agregació els necessiten.
  • Saps que l'script és idempotent: sempre pots tornar a l'estat inicial rellançant-lo.

Has completat el mòdul 1. A hores d'ara entens què és SQL i quin lloc ocupa, tens PostgreSQL 16 funcionant, domines les regles d'escriptura del llenguatge, saps com s'estructuren i es tipen les dades, comprens per què les taules es relacionen com ho fan i tens carregada la base de dades que t'acompanyarà fins al projecte final. Al mòdul 2, Consultes bàsiques de SQL, començaràs per fi a interrogar BotigaVerda: la instrucció SELECT i com triar columnes, els àlies i les columnes calculades, el filtratge amb WHERE, l'eliminació de duplicats amb DISTINCT, l'ordenació amb ORDER BY i la limitació de resultats amb LIMIT. La primera consulta real és a una lliçó de distància.

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