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
- El negoci: què és BotigaVerda i com opera
- Diagrama entitat-relació
- Les nou taules, una a una
- Decisions de disseny que convé conèixer
- L'script de creació de taules
- L'script de càrrega de dades
- Com carregar la base de dades
- Consultes de verificació
- Errors habituals i consells
- Exercicis
- Conclusió
- 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_idpugui serNULL, i serà protagonista de les lliçons deLEFT JOINi 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:
pendent→pagat→enviat→lliurat, amb la possibilitat decancellaten 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?
- 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).
- 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 |
Sí | 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) |
Sí | 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 |
Sí | FK → categories.id. ON DELETE RESTRICT |
proveidor_id |
INTEGER |
Sí | FK → proveidors.id. ON DELETE RESTRICT |
preu |
NUMERIC(10,2) |
No | Preu de venda al públic, en euros |
cost |
NUMERIC(10,2) |
Sí | 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 deLEFT 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) |
Sí | 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 |
Sí | 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 |
Sí | FK → empleats.id (reflexiva). NULL només a direcció general. ON DELETE SET NULL |
salari |
NUMERIC(10,2) |
Sí | Salari brut anual en euros |
data_contractacio |
DATE |
No | Data d'incorporació |
ciutat |
VARCHAR(80) |
Sí | 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 |
Sí | 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 |
Sí | 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.
- 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 |
- 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:
- Les taules es creen en ordre de dependències.
productesreferenciacategoriesiproveidors, així que aquestes dues van abans. L'esborrat (DROP) es fa en l'ordre invers. - 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.
- 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.
- 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)
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
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.
- Consultes de verificació
Comprova que tot és on ha de ser.
8.1. Les nou taules existeixen
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
| 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 |
| 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
INSERTtallat 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_unitarino coincideixi ambpreu. A les línies 1 i 4 és intencionat: són preus històrics anteriors a la pujada de tarifes. - Interpretar
descomptecom a percentatge. És una fracció:0.10significa 10 %. L'import d'una línia ésquantitat * preu_unitari * (1 - descompte). - Esperar ressenyes de tots els productes. Només 9 dels 20 productes tenen ressenyes, i això és deliberat.
- Oblidar el
setvalfinal. Sense ell, el primerINSERTsenseidexplí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 taulaabans 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.sqlet 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:
- Existeixen les nou taules?
- Quantes files té cadascuna?
- Quines columnes, tipus i claus foranes té
linies_comanda? - 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.
- Quina és la puntuació mitjana del producte "Oli d'oliva verge extra 500 ml"?
- Quin comercial va gestionar la comanda número 12 i qui és el seu cap?
- Quant va facturar la comanda 8, despeses d'enviament incloses?
- De quin país és el proveïdor del producte més car del catàleg?
- 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
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:
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:
| count |
|---|
| 10 |
Solució 2
| # | Pregunta | Camí entre taules | Columnes clau |
|---|---|---|---|
| 1 | Puntuació mitjana d'un producte | productes → ressenyes |
productes.nom, ressenyes.producte_id, ressenyes.puntuacio |
| 2 | Comercial de la comanda 12 i el seu cap | comandes → empleats → empleats (self join) |
comandes.empleat_id, empleats.id, empleats.cap_id |
| 3 | Facturació de la comanda 8 | comandes → linies_comanda |
linies_comanda.quantitat, preu_unitari, descompte, més comandes.despeses_enviament |
| 4 | País del proveïdor del producte més car | productes → proveidors |
productes.preu, productes.proveidor_id, proveidors.pais |
| 5 | Clients referits per la Lucía | clients → clients (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".
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:
| 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_comandai dues relacions reflexives aclientsiempleats. - 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,NULLi 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
- 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
