Les set taules de BiblioRed existeixen i són buides. Aquesta lliçó les omple i ensenya a interrogar-les. És la lliçó més pràctica del mòdul: aquí aprens les quatre operacions que cobreixen el 90 % de la feina diària amb una base de dades —inserir, consultar, modificar i esborrar—, conegudes col·lectivament com a CRUD (Create, Read, Update, Delete).

Ens limitem deliberadament a una sola taula per consulta. Recompondre informació repartida entre diverses taules és un tema amb prou substància per a la seva pròpia lliçó (02-04), i agrupar i resumir, per a la següent (02-05). Aquí construïm els fonaments: sense dominar WHERE i ORDER BY sobre una taula, cap JOIN no sortirà bé.

El lliurable de la lliçó és el joc de dades de prova de BiblioRed: quatre sucursals, vuit autors, deu socis, nou llibres, quinze exemplars, dotze préstecs i cinc reserves. Tots ficticis i tots coherents entre si. Els farem servir fins al final del curs, així que executa'ls amb atenció i guarda'ls en un fitxer.

Contingut

  1. INSERT: donar d'alta files
  2. Lliurable: el joc de dades de BiblioRed
  3. Reajustar els generadors d'identificadors
  4. SELECT: projecció, àlies i DISTINCT
  5. WHERE: filtrar files
  6. ORDER BY: ordenar el resultat
  7. LIMIT i OFFSET: paginació
  8. UPDATE: modificar files existents
  9. DELETE: eliminar files
  10. TRUNCATE: buidar una taula sencera
  11. Errors habituals i consells
  12. Exercicis
  13. Conclusió

  1. INSERT: donar d'alta files

Forma bàsica

INSERT INTO taula (columna1, columna2, ...) VALUES (valor1, valor2, ...);

Comencem per les sucursals de BiblioRed. La xarxa opera a la ciutat fictícia de Vallmar i té quatre biblioteques.

INSERT INTO sucursals (nom, adreca, telefon, data_obertura)
VALUES ('Centre', 'Plaça Major 1', '900 100 001', '1998-04-12');
INSERT 0 1

La sortida de PostgreSQL es llegeix així: INSERT <oid> <nombre de files inserides>. El primer número és un vestigi històric i sempre val 0; el segon és el que importa.

Fixa't en tres coses:

  • No hem donat sucursal_id. La columna és GENERATED BY DEFAULT AS IDENTITY, així que PostgreSQL li assigna l'1.
  • La data va entre cometes simples, en format ISO AAAA-MM-DD. El gestor la converteix a DATE perquè la columna és d'aquest tipus.
  • El telèfon va entre cometes, encara que sembli un número: és VARCHAR, com vam decidir a la lliçó anterior.

Sense llista de columnes: la forma fràgil

SQL permet ometre la llista de columnes si dónes un valor per a totes, en l'ordre exacte de la definició de la taula:

-- Legal, però no ho facis
INSERT INTO sucursals VALUES (DEFAULT, 'Nord', 'Avinguda del Parc 45', '900 100 002', '2005-09-30');

Per què evitar-ho? Perquè el dia que algú afegeixi una columna amb ALTER TABLE, o reordeni la definició, tots els INSERT sense llista de columnes es trencaran o, pitjor, inseriran valors a la columna equivocada sense donar error. Escriu sempre la llista de columnes. És la primera regla d'higiene del SQL professional.

Fem-ho bé:

INSERT INTO sucursals (nom, adreca, telefon, data_obertura)
VALUES ('Nord', 'Avinguda del Parc 45', '900 100 002', '2005-09-30');

Aquesta és la sucursal Nord, que a partir d'ara tindrà sucursal_id = 2.

Inserir diverses files de cop

Es poden encadenar diverses tuples de valors separades per comes. És més ràpid (una sola operació en lloc de N) i més llegible:

INSERT INTO sucursals (nom, adreca, telefon, data_obertura) VALUES
    ('Sud',  'Carrer Olivar 12',   '900 100 003', '2011-02-18'),
    ('Est',  'Ronda del Port 8',   NULL,          '2019-06-25');
INSERT 0 2

La sucursal Est encara no té telèfon propi: escrivim NULL sense cometes. Si escrivíssim 'NULL' estaríem guardant la cadena de quatre lletres N-U-L-L, que no és el mateix en absolut.

Una altra opció és ometre la columna de la llista: el que no s'esmenta queda a NULL (o al seu valor per defecte, si en tingués).

-- (Només il·lustratiu, NO ho executis: crearia una segona sucursal Est.)
-- Equivalent per al telèfon, però explícit és millor que implícit.
INSERT INTO sucursals (nom, adreca, data_obertura)
VALUES ('Est', 'Ronda del Port 8', '2019-06-25');

RETURNING: saber quin identificador s'ha generat

Quan deixes que el gestor generi la clau, sorgeix un problema pràctic immediat: quin número li ha tocat? El necessitaràs per inserir les files filles. PostgreSQL ho resol amb RETURNING:

INSERT INTO autors (nom, cognoms, nacionalitat, any_naixement)
VALUES ('Félix J.', 'Palma', 'espanyola', 1968)
RETURNING autor_id, cognoms;
 autor_id | cognoms
----------+---------
        1 | Palma
(1 row)

RETURNING funciona igual amb UPDATE i DELETE, i pot retornar qualsevol expressió, inclòs *. És una extensió de PostgreSQL molt còmoda que evita el clàssic "insereixo i després consulto".

SQLite no té RETURNING fins a la versió 3.35 (2021); en versions anteriors es fa servir la funció last_insert_rowid():

INSERT INTO autors (nom, cognoms) VALUES ('Félix J.', 'Palma');
SELECT last_insert_rowid();

Què comprova el gestor a cada INSERT

Abans d'acceptar la fila, el gestor verifica les tres regles d'integritat de la lliçó 02-01:

INSERT INTO sucursals (nom, adreca) VALUES ('Nord', 'Una altra adreça');
ERROR:  duplicate key value violates unique constraint "uq_sucursals_nom"
DETAIL:  Key (nom)=(Nord) already exists.
INSERT INTO socis (nom, cognoms, data_alta, sucursal_id, actiu)
VALUES ('Prova', 'Prova', '2026-01-01', 99, TRUE);
ERROR:  insert or update on table "socis" violates foreign key constraint "fk_socis_sucursal"
DETAIL:  Key (sucursal_id)=(99) is not present in table "sucursals".

Això és exactament el que el full de càlcul de BiblioRed no feia. Aquí l'error no és una molèstia: és el sistema fent la seva feina.

  1. Lliurable: el joc de dades de BiblioRed

Aquest és l'script de càrrega. Executa'l sencer i en aquest ordre (les filles necessiten les pares) i guarda'l com a dades_biblioredb.sql.

Un matís sobre els identificadors que hi veuràs:

  • A sucursals, autors, exemplars, prestecs i reserves deixem que el gestor els generi.
  • A socis i llibres els imposem: BiblioRed està migrant des del full de càlcul i vol conservar els números de carnet (Marta Alsina és la sòcia 14 des del 2021) i les signatures del catàleg antic (331 és "El mapa del temps"). És un cas realíssim, i a l'apartat 3 veurem l'efecte secundari que provoca.
-- ============================================================
--  BiblioRed - Joc de dades de prova (tots ficticis)
--  Mòdul 2, lliçó 02-03. Dialecte: PostgreSQL
-- ============================================================

-- PAS 0: partir de zero. Els exemples de l'apartat anterior ja
-- van inserir dues sucursals i un autor; els esborrem perquè
-- l'script sigui l'única font de dades. L'ordre és l'invers al
-- de dependències: primer les filles, després les pares.
DELETE FROM reserves;
DELETE FROM prestecs;
DELETE FROM exemplars;
DELETE FROM llibres;
DELETE FROM socis;
DELETE FROM autors;
DELETE FROM sucursals;
ALTER TABLE sucursals ALTER COLUMN sucursal_id RESTART WITH 1;
ALTER TABLE autors    ALTER COLUMN autor_id    RESTART WITH 1;
ALTER TABLE exemplars ALTER COLUMN exemplar_id RESTART WITH 1;
ALTER TABLE prestecs  ALTER COLUMN prestec_id  RESTART WITH 1;
ALTER TABLE reserves  ALTER COLUMN reserva_id  RESTART WITH 1;

-- SUCURSALS: les quatre biblioteques de la xarxa de Vallmar.
-- Identificadors 1..4 generats pel gestor: Centre=1, Nord=2, Sud=3, Est=4.
INSERT INTO sucursals (nom, adreca, telefon, data_obertura) VALUES
    ('Centre', 'Plaça Major 1',        '900 100 001', '1998-04-12'),
    ('Nord',   'Avinguda del Parc 45', '900 100 002', '2005-09-30'),
    ('Sud',    'Carrer Olivar 12',     '900 100 003', '2011-02-18'),
    ('Est',    'Ronda del Port 8',     NULL,          '2019-06-25');

-- AUTORS: identificadors 1..8 generats pel gestor.
-- Marina Escolà (8) encara no té cap obra al fons:
-- ens servirà per practicar els LEFT JOIN de la propera lliçó.
INSERT INTO autors (nom, cognoms, nacionalitat, any_naixement) VALUES
    ('Félix J.', 'Palma',      'espanyola',  1968),
    ('Ken',      'Follett',    'britànica',  1949),
    ('Irene',    'Valcárcel',  'espanyola',  1975),
    ('Óscar',    'Barreda',    'espanyola',  1981),
    ('Nadia',    'Sorrentino', 'italiana',   1970),
    ('Hugo',     'Lemos',      'portuguesa', 1958),
    ('Clara',    'Ordóñez',    'espanyola',  1988),
    ('Marina',   'Escolà',     'espanyola',  1992);

-- SOCIS: números de carnet heretats del full de càlcul (11..20).
-- Pau Miralles (19) no va facilitar correu: el seu email queda a NULL.
-- Ramón Etxebarri (13) està de baixa: actiu = FALSE.
INSERT INTO socis (soci_id, nom, cognoms, email, data_alta, sucursal_id, actiu) VALUES
    (11, 'Álvaro', 'Ferran',    '[email protected]',   '2018-01-22', 1, TRUE),
    (12, 'Sonia',  'Quiroga',   '[email protected]',   '2019-05-03', 3, TRUE),
    (13, 'Ramón',  'Etxebarri', '[email protected]', '2020-11-14', 1, FALSE),
    (14, 'Marta',  'Alsina',    '[email protected]',    '2021-03-08', 2, TRUE),
    (15, 'Ivan',   'Pereda',    '[email protected]',     '2021-09-19', 2, TRUE),
    (16, 'Núria',  'Bastos',    '[email protected]',    '2022-01-30', 1, TRUE),
    (17, 'Diego',  'Salom',     '[email protected]',     '2023-02-11', 3, TRUE),
    (18, 'Lucía',  'Vendrell',  '[email protected]',  '2023-07-05', 4, TRUE),
    (19, 'Pau',    'Miralles',  NULL,                          '2024-04-16', 2, TRUE),
    (20, 'Elena',  'Roig',      '[email protected]',      '2024-10-01', 1, TRUE);

-- LLIBRES: signatures heretades (331..339).
-- El 339 és una publicació municipal antiga: SENSE ISBN i SENSE autor
-- catalogat. És la prova vivent de per què l'ISBN no podia ser
-- clau primària (lliçó 02-01).
INSERT INTO llibres (llibre_id, isbn, titol, autor_id, editorial, any_publicacio, idioma) VALUES
    (331, '9788401339097', 'El mapa del temps',            1,    'Editorial Andana',     2008, 'es'),
    (332, '9788401337208', 'Els pilars de la Terra',       2,    'Editorial Andana',     1989, 'es'),
    (333, '9788412007701', 'La casa de les marees',        3,    'Edicions Marlia',      2015, 'es'),
    (334, '9788412007702', 'Àlgebra per a impacients',     4,    'Premsa Tècnica Nord',  2019, 'es'),
    (335, '9788412007703', 'Quaderns de Ravenna',          5,    'Edicions Marlia',      2012, 'es'),
    (336, '9788412007704', 'L''hivern dels ocells',        6,    'Editorial Andana',     2021, 'es'),
    (337, '9788412007705', 'Rutes del delta',              7,    'Edicions Marlia',      2017, 'ca'),
    (338, '9788412007706', 'Manual de jardineria urbana',  4,    'Premsa Tècnica Nord',  2023, 'es'),
    (339, NULL,            'Memòria de l''Eixample (1904)', NULL, 'Ajuntament de Vallmar', 1904, 'es');

-- EXEMPLARS: els objectes físics. Identificadors 1..15 generats
-- en l'ordre d'aquesta llista; EJ-3081 serà l'exemplar_id 1.
INSERT INTO exemplars (codi, llibre_id, sucursal_id, estat, data_adquisicio) VALUES
    ('EJ-3081', 331, 2, 'prestat',    '2019-03-14'),
    ('EJ-3082', 331, 1, 'disponible', '2019-03-14'),
    ('EJ-3083', 331, 3, 'disponible', '2021-06-01'),
    ('EJ-3084', 332, 1, 'prestat',    '2015-11-20'),
    ('EJ-3085', 332, 2, 'disponible', '2015-11-20'),
    ('EJ-3086', 333, 1, 'prestat',    '2016-02-09'),
    ('EJ-3087', 334, 2, 'disponible', '2020-01-15'),
    ('EJ-3088', 334, 4, 'reparacio',  '2020-01-15'),
    ('EJ-3089', 335, 3, 'disponible', '2013-05-04'),
    ('EJ-3090', 336, 2, 'prestat',    '2022-04-27'),
    ('EJ-3091', 336, 1, 'disponible', '2022-04-27'),
    ('EJ-3092', 337, 4, 'disponible', '2018-10-02'),
    ('EJ-3093', 338, 1, 'disponible', '2023-09-11'),
    ('EJ-3094', 338, 3, 'baixa',      '2023-09-11'),
    ('EJ-3095', 339, 1, 'disponible', '2003-01-15');

-- PRESTECS: vuit tancats i quatre oberts.
-- data_devolucio NULL = préstec encara en curs.
-- Els quatre préstecs oberts corresponen als quatre exemplars
-- l'estat dels quals és 'prestat' (1, 4, 6 i 10). Coherència total.
INSERT INTO prestecs
    (soci_id, exemplar_id, data_prestec, data_devolucio_prevista, data_devolucio, recarrec) VALUES
    (14,  2, '2026-03-02', '2026-03-23', '2026-03-19', 0.00),
    (15,  1, '2026-03-05', '2026-03-26', '2026-04-02', 1.40),
    (16,  5, '2026-03-11', '2026-04-01', '2026-03-30', 0.00),
    (14,  7, '2026-04-06', '2026-04-27', '2026-04-25', 0.00),
    (18,  9, '2026-04-12', '2026-05-03', '2026-05-10', 1.40),
    (16,  4, '2026-05-04', '2026-05-25', '2026-05-22', 0.00),
    (11,  1, '2026-05-08', '2026-05-29', '2026-05-27', 0.00),
    (12, 12, '2026-05-19', '2026-06-09', '2026-06-30', 4.20),
    (14,  1, '2026-07-14', '2026-08-04', NULL,         NULL),
    (15,  4, '2026-07-18', '2026-08-08', NULL,         NULL),
    (17,  6, '2026-07-21', '2026-08-11', NULL,         NULL),
    (19, 10, '2026-07-25', '2026-08-15', NULL,         NULL);

-- RESERVES: apunten al LLIBRE (l'obra), no a l'exemplar.
INSERT INTO reserves (soci_id, llibre_id, data_reserva, data_expiracio, estat) VALUES
    (16, 331, '2026-07-20', '2026-08-10', 'activa'),
    (18, 332, '2026-07-22', '2026-08-12', 'activa'),
    (14, 336, '2026-06-30', '2026-07-20', 'atesa'),
    (11, 333, '2026-07-05', '2026-07-25', 'cancellada'),
    (15, 331, '2026-07-28', '2026-08-18', 'activa');

Comprovació de la càrrega

SELECT 'sucursals' AS taula, COUNT(*) AS files FROM sucursals
UNION ALL SELECT 'autors',    COUNT(*) FROM autors
UNION ALL SELECT 'socis',     COUNT(*) FROM socis
UNION ALL SELECT 'llibres',   COUNT(*) FROM llibres
UNION ALL SELECT 'exemplars', COUNT(*) FROM exemplars
UNION ALL SELECT 'prestecs',  COUNT(*) FROM prestecs
UNION ALL SELECT 'reserves',  COUNT(*) FROM reserves;

(COUNT i UNION ALL són de les lliçons 02-05 i 02-04; aquí només ho fem servir com a recompte de control.)

taula files
sucursals 4
autors 8
socis 10
llibres 9
exemplars 15
prestecs 12
reserves 5

I la correspondència entre codis i identificadors d'exemplar, que necessitaràs per entendre els préstecs:

SELECT exemplar_id, codi, llibre_id FROM exemplars ORDER BY exemplar_id LIMIT 4;
exemplar_id codi llibre_id
1 EJ-3081 331
2 EJ-3082 331
3 EJ-3083 331
4 EJ-3084 332

Si els teus recomptes coincideixen, tens el mateix joc de dades que la resta del curs.

Notes per a SQLite

Tres canvis a l'script:

PRAGMA foreign_keys = ON;   -- a cada sessió!
  • TRUE/FALSE a socis.actiu1/0.
  • Les línies ALTER TABLE ... RESTART WITH del pas 0 no existeixen a SQLite: elimina-les. Amb INTEGER PRIMARY KEY sense AUTOINCREMENT, el següent identificador es calcula sol a partir del màxim existent.
  • Les dates s'escriuen igual ('2026-03-02'), però es guarden com a text. Mentre facis servir el format ISO, les comparacions i ordenacions continuaran funcionant, perquè l'ordre alfabètic d'AAAA-MM-DD coincideix amb el cronològic. És exactament la raó per la qual aquest format és el bo.

  1. Reajustar els generadors d'identificadors

Aquí arriba l'efecte secundari promès. A socis i llibres vam inserir identificadors explícits, però el generador de la columna no se n'ha assabentat: continua apuntant a l'1. Comprovem-ho donant d'alta un soci sense indicar-ne l'identificador:

INSERT INTO socis (nom, cognoms, data_alta, sucursal_id, actiu)
VALUES ('Soci', 'Deprova', '2026-08-01', 1, TRUE)
RETURNING soci_id;
 soci_id
---------
       1
(1 row)

El nou soci ha rebut l'1, quan els carnets de BiblioRed comencen a l'11. No ha fallat res, però el desajust ja està sembrat: els deu següents que es donin d'alta s'endurien el 2, el 3… i l'onzè xocaria:

ERROR:  duplicate key value violates unique constraint "pk_socis"
DETAIL:  Key (soci_id)=(11) already exists.

És un dels errors més desconcertants per a qui comença, perquè apareix setmanes després de la càrrega i sense relació aparent amb ella. La causa sempre és la mateixa: s'han inserit claus a mà sense resincronitzar el generador. Esborrem el soci de prova i arreglem-ho:

DELETE FROM socis WHERE cognoms = 'Deprova';

La solució a PostgreSQL:

ALTER TABLE socis   ALTER COLUMN soci_id   RESTART WITH 21;
ALTER TABLE llibres ALTER COLUMN llibre_id RESTART WITH 340;

O, de manera automàtica i sense haver de mirar el màxim a ull:

SELECT setval(pg_get_serial_sequence('socis', 'soci_id'),
              (SELECT MAX(soci_id) FROM socis));

A SQLite el problema no existeix si la clau es va declarar com a INTEGER PRIMARY KEY sense AUTOINCREMENT: el valor següent es calcula com el màxim actual més un, així que s'ajusta sol.

  1. SELECT: projecció, àlies i DISTINCT

SELECT és la instrucció més utilitzada de SQL i la que més lliçons ocupa en aquest curs. La seva forma mínima:

SELECT columna1, columna2 FROM taula;

Projecció: triar columnes

SELECT nom, cognoms, data_alta FROM socis;
nom cognoms data_alta
Álvaro Ferran 2018-01-22
Sonia Quiroga 2019-05-03
Ramón Etxebarri 2020-11-14
Marta Alsina 2021-03-08
Ivan Pereda 2021-09-19
Núria Bastos 2022-01-30
Diego Salom 2023-02-11
Lucía Vendrell 2023-07-05
Pau Miralles 2024-04-16
Elena Roig 2024-10-01

Això és la projecció π de l'àlgebra relacional. Recorda que l'ordre en què apareixen les files no està garantit sense ORDER BY: aquí surten en ordre d'inserció perquè la taula és petita i acabada de carregar, però no hi comptis.

SELECT *: còmode i perillós

SELECT * FROM sucursals;
sucursal_id nom adreca telefon data_obertura
1 Centre Plaça Major 1 900 100 001 1998-04-12
2 Nord Avinguda del Parc 45 900 100 002 2005-09-30
3 Sud Carrer Olivar 12 900 100 003 2011-02-18
4 Est Ronda del Port 8 (NULL) 2019-06-25

* significa "totes les columnes". Va perfecte per explorar a la consola, però no el facis servir en codi d'aplicació: portes dades que no necessites per la xarxa, i si demà algú afegeix una columna, el teu programa rep alguna cosa que no esperava. En un script guardat, columnes explícites.

Observa que el NULL del telèfon d'Est apareix com a cel·la buida a psql. El pots fer visible:

biblioredb=> \pset null '(nul)'

Àlies amb AS

Un àlies reanomena una columna al resultat, sense tocar la taula. És l'operador de reanomenament ρ de l'àlgebra relacional.

SELECT codi   AS etiqueta,
       estat  AS situacio,
       data_adquisicio AS "data de compra"
FROM exemplars
WHERE sucursal_id = 4;
etiqueta situacio data de compra
EJ-3088 reparacio 2020-01-15
EJ-3092 disponible 2018-10-02

Punts a retenir:

  • AS és opcional (codi etiqueta funciona igual), però escriure'l fa el SQL molt més llegible.
  • Perquè un àlies porti espais, majúscules o accents cal posar-lo entre cometes dobles: és un identificador. Aquí sí que és acceptable, perquè l'àlies només viu a la sortida.

També es poden calcular columnes noves:

SELECT nom || ' ' || cognoms AS nom_complet,
       data_alta
FROM socis
WHERE sucursal_id = 2;
nom_complet data_alta
Marta Alsina 2021-03-08
Ivan Pereda 2021-09-19
Pau Miralles 2024-04-16

|| és l'operador estàndard de concatenació (funciona a PostgreSQL i SQLite; MySQL fa servir CONCAT()).

Compte amb NULL a les concatenacions: 'Pau' || NULL dóna NULL, no 'Pau'. Si cognoms pogués ser nul, el nom complet desapareixeria sencer. A 02-05 veurem COALESCE, que resol exactament això.

DISTINCT: eliminar duplicats

SELECT estat FROM exemplars;

Retorna 15 files amb moltes repeticions. Amb DISTINCT:

SELECT DISTINCT estat FROM exemplars ORDER BY estat;
estat
baixa
disponible
prestat
reparacio

DISTINCT s'aplica a la combinació completa de columnes seleccionades, no a la primera:

SELECT DISTINCT sucursal_id, estat
FROM exemplars
ORDER BY sucursal_id, estat;
sucursal_id estat
1 disponible
1 prestat
2 disponible
2 prestat
3 baixa
3 disponible
4 disponible
4 reparacio

Són les vuit combinacions diferents que existeixen, de les 15 files originals.

Recorda de la lliçó 02-01: a l'àlgebra relacional la projecció sempre elimina duplicats; en SQL cal demanar-ho. DISTINCT obliga el gestor a ordenar o a construir una taula de dispersió, així que té cost: no el posis "per si de cas".

  1. WHERE: filtrar files

WHERE és la selecció σ de l'àlgebra: es queda amb les files la condició de les quals sigui CERTA (recorda: DESCONEGUT no passa).

Operadors de comparació

Operador Significat
= Igual
<> o != Diferent
<, >, <=, >= Menor, major, menor o igual, major o igual
SELECT titol, any_publicacio
FROM llibres
WHERE any_publicacio > 2015
ORDER BY any_publicacio;
titol any_publicacio
Rutes del delta 2017
Àlgebra per a impacients 2019
L'hivern dels ocells 2021
Manual de jardineria urbana 2023

Les comparacions funcionen també sobre text (ordre alfabètic segons la configuració regional) i sobre dates:

SELECT nom, cognoms, data_alta
FROM socis
WHERE data_alta >= '2023-01-01'
ORDER BY data_alta;
nom cognoms data_alta
Diego Salom 2023-02-11
Lucía Vendrell 2023-07-05
Pau Miralles 2024-04-16
Elena Roig 2024-10-01

Operadors lògics: AND, OR, NOT

SELECT codi, estat, sucursal_id
FROM exemplars
WHERE sucursal_id = 1 AND estat = 'disponible';
codi estat sucursal_id
EJ-3082 disponible 1
EJ-3091 disponible 1
EJ-3093 disponible 1
EJ-3095 disponible 1

AND té més precedència que OR, igual que la multiplicació sobre la suma. Això provoca errors silenciosos:

-- El que es volia: els exemplars de les sucursals 1 o 2 que estiguin prestats
-- El que fa: els de la sucursal 1 (en qualsevol estat)
--            MÉS els de la 2 que estiguin prestats
SELECT codi, sucursal_id, estat FROM exemplars
WHERE sucursal_id = 1 OR sucursal_id = 2 AND estat = 'prestat';

Retorna 7 files: els sis de la sucursal 1 més EJ-3081. Amb parèntesis:

SELECT codi, sucursal_id, estat FROM exemplars
WHERE (sucursal_id = 1 OR sucursal_id = 2) AND estat = 'prestat';
codi sucursal_id estat
EJ-3081 2 prestat
EJ-3084 1 prestat
EJ-3086 1 prestat

Consell: fes servir parèntesis sempre que barregis AND i OR, encara que sàpigues la precedència. Qui llegeixi la teva consulta d'aquí a un any t'ho agrairà.

BETWEEN: rangs

SELECT titol, any_publicacio
FROM llibres
WHERE any_publicacio BETWEEN 2010 AND 2019
ORDER BY any_publicacio;
titol any_publicacio
Quaderns de Ravenna 2012
La casa de les marees 2015
Rutes del delta 2017
Àlgebra per a impacients 2019

BETWEEN a AND b és sucre sintàctic de >= a AND <= b: tots dos extrems hi són inclosos. És la font d'un error clàssic amb dates i hores: BETWEEN '2026-03-01' AND '2026-03-31' sobre una columna TIMESTAMP deixa fora tot el que va passar el dia 31 després de mitjanit, perquè 2026-03-31 09:00 és més gran que 2026-03-31 00:00. Amb columnes DATE com les de BiblioRed no hi ha problema.

IN: pertinença a una llista

SELECT codi, estat
FROM exemplars
WHERE estat IN ('reparacio', 'baixa');
codi estat
EJ-3088 reparacio
EJ-3094 baixa

IN equival a una cadena d'OR, però és molt més llegible. També existeix NOT IN:

SELECT codi, estat FROM exemplars WHERE estat NOT IN ('disponible', 'prestat');

Mateix resultat que abans.

Avís important sobre NOT IN i NULL: si la llista conté un NULL, NOT IN no retorna cap fila, per la lògica de tres valors de la lliçó 02-01. x NOT IN (1, 2, NULL) equival a x <> 1 AND x <> 2 AND x <> NULL, i aquest últim terme és sempre DESCONEGUT. Amb llistes escrites a mà no passa; amb llistes que vénen d'una subconsulta (lliçó 02-04) és un parany habitual.

LIKE i ILIKE: cerca de patrons en text

Dos comodins:

Comodí Significa
% Zero o més caràcters qualssevol
_ Exactament un caràcter qualsevol
SELECT titol FROM llibres WHERE titol LIKE '%del%';
titol
El mapa del temps
L'hivern dels ocells
Rutes del delta

LIKE distingeix majúscules i minúscules a PostgreSQL:

SELECT titol FROM llibres WHERE titol LIKE 'el%';
(0 rows)

Cap títol no comença per el en minúscula. Per ignorar la caixa, PostgreSQL ofereix ILIKE (la I és d'insensitive), que no és estàndard però és comodíssim:

SELECT titol FROM llibres WHERE titol ILIKE 'el%';
titol
El mapa del temps
Els pilars de la Terra

Alternativa portable, vàlida també a SQLite:

SELECT titol FROM llibres WHERE LOWER(titol) LIKE 'el%';

A SQLite, LIKE ja és insensible a majúscules per als caràcters ASCII (però no per a les vocals accentuades), i no existeix ILIKE. És una de les diferències que més sorprenen en portar consultes.

Exemple amb _:

SELECT codi FROM exemplars WHERE codi LIKE 'EJ-308_';
codi
EJ-3080…EJ-3089 → els deu de la primera desena

Concretament retorna EJ-3081 a EJ-3089: nou files, perquè EJ-3080 no existeix.

IS NULL: l'absència de valor

Aquí tornem al parany que vam anunciar a 02-01.

-- MALAMENT: zero files, sempre, sense error
SELECT soci_id, nom FROM socis WHERE email = NULL;
(0 rows)
-- BÉ
SELECT soci_id, nom, cognoms FROM socis WHERE email IS NULL;
soci_id nom cognoms
19 Pau Miralles

I el seu complementari, que a BiblioRed té un significat molt concret:

-- Els préstecs encara oberts: no hi ha data de devolució
SELECT prestec_id, soci_id, exemplar_id, data_devolucio_prevista
FROM prestecs
WHERE data_devolucio IS NULL
ORDER BY data_devolucio_prevista;
prestec_id soci_id exemplar_id data_devolucio_prevista
9 14 1 2026-08-04
10 15 4 2026-08-08
11 17 6 2026-08-11
12 19 10 2026-08-15

Quatre préstecs oberts, que coincideixen exactament amb els quatre exemplars en estat prestat. La base de dades és coherent.

I una comprovació de l'altre parany dels NULL, el de les desigualtats:

SELECT COUNT(*) FROM prestecs WHERE recarrec <> 0;   -- 3
SELECT COUNT(*) FROM prestecs WHERE recarrec = 0;    -- 5
-- 3 + 5 = 8, no 12: falten els quatre préstecs amb recàrrec NULL

Cap de les dues consultes no veu els NULL. Si vols els préstecs "sense recàrrec pendent", ho has de dir: WHERE recarrec = 0 OR recarrec IS NULL.

  1. ORDER BY: ordenar el resultat

Com que una relació és un conjunt i no té ordre, ORDER BY és l'única manera de garantir-lo.

SELECT titol, any_publicacio FROM llibres ORDER BY any_publicacio DESC;
titol any_publicacio
Manual de jardineria urbana 2023
L'hivern dels ocells 2021
Àlgebra per a impacients 2019
Rutes del delta 2017
La casa de les marees 2015
Quaderns de Ravenna 2012
El mapa del temps 2008
Els pilars de la Terra 1989
Memòria de l'Eixample (1904) 1904

ASC (ascendent) és el valor per defecte; DESC inverteix.

Diversos criteris

S'ordena pel primer i, dins dels empats, pel segon:

SELECT sucursal_id, cognoms, nom
FROM socis
ORDER BY sucursal_id ASC, cognoms ASC;
sucursal_id cognoms nom
1 Bastos Núria
1 Etxebarri Ramón
1 Ferran Álvaro
1 Roig Elena
2 Alsina Marta
2 Miralles Pau
2 Pereda Ivan
3 Quiroga Sonia
3 Salom Diego
4 Vendrell Lucía

Cada criteri porta el seu propi ASC/DESC: ORDER BY sucursal_id ASC, data_alta DESC és perfectament vàlid.

NULLS FIRST i NULLS LAST

On va un NULL en ordenar? L'estàndard deixa llibertat, i PostgreSQL els col·loca al final en ASC i al principi en DESC (equival a tractar-los com el valor més gran). SQLite fa el contrari: els posa al principi en ASC.

Com que no volem dependre del gestor, s'especifica:

SELECT prestec_id, data_devolucio
FROM prestecs
ORDER BY data_devolucio DESC NULLS LAST, prestec_id;
prestec_id data_devolucio
8 2026-06-30
7 2026-05-27
6 2026-05-22
5 2026-05-10
4 2026-04-25
2 2026-04-02
3 2026-03-30
1 2026-03-19
9 (NULL)
10 (NULL)
11 (NULL)
12 (NULL)

Fixa't en el segon criteri, prestec_id: sense ell, l'ordre entre els quatre NULL seria arbitrari. Quan l'ordre importi de veritat, acaba sempre amb un criteri que desempati sense ambigüitat, típicament la clau primària.

NULLS FIRST/NULLS LAST és sintaxi estàndard i funciona a PostgreSQL; SQLite l'admet des de la versió 3.30.

Ordenar per àlies o per posició

SELECT nom || ' ' || cognoms AS nom_complet FROM socis ORDER BY nom_complet;
SELECT nom, cognoms FROM socis ORDER BY 2;   -- per la 2a columna: cognoms

Ordenar per àlies és legítim i llegible. Ordenar per número de posició funciona, però és fràgil: si algú reordena la llista del SELECT, la consulta canvia de sentit en silenci. Evita-ho.

Ordenació de text i accents

ORDER BY titol situa "Àlgebra per a impacients" en primer lloc si la base de dades fa servir una configuració regional catalana, perquè À s'ordena al costat de la A. Amb la configuració C (byte a byte), À aniria després de la Z. No és una errada: és la col·lació. Pots veure la teva amb SHOW lc_collate; a PostgreSQL.

  1. LIMIT i OFFSET: paginació

SELECT titol FROM llibres ORDER BY titol LIMIT 3;
titol
Àlgebra per a impacients
El mapa del temps
Els pilars de la Terra

OFFSET salta files abans de començar a comptar:

SELECT titol FROM llibres ORDER BY titol LIMIT 3 OFFSET 3;
titol
La casa de les marees
L'hivern dels ocells
Manual de jardineria urbana

Aquesta és la mecànica de la paginació de qualsevol catàleg web: la pàgina N s'obté amb LIMIT mida OFFSET (N-1) * mida.

Dos advertiments:

  1. LIMIT sense ORDER BY no té sentit. "Dóna'm 3 files de les 9" sense dir quines significa "dóna'm 3 de qualssevol", i poden ser diferents a cada execució. Encara pitjor: si pagines sense ordenar, la mateixa fila pot sortir a la pàgina 1 i a la 3, i una altra no sortir mai.
  2. Un OFFSET gran és lent. Per arribar a la fila 100.000 el gestor ha de produir i descartar les 100.000 anteriors. En catàlegs grans es fan servir tècniques de paginació per clau; és tema de rendiment (lliçó 06-03).

La sintaxi estàndard és OFFSET 3 ROWS FETCH FIRST 3 ROWS ONLY, més verbosa i menys utilitzada. LIMIT/OFFSET funciona a PostgreSQL, SQLite i MySQL.

  1. UPDATE: modificar files existents

UPDATE taula SET columna1 = valor1, columna2 = valor2 WHERE condició;

Abans de començar: els exemples d'aquest apartat i del següent modifiquen el joc de dades que acabem de carregar, i les lliçons 02-04 i 02-05 el donen per bo. Al final de cada exemple hi incloem la instrucció que desfà el canvi. Executa-la.

L'hàbit que salva bases de dades

Abans de qualsevol UPDATE o DELETE, executa la mateixa condició amb un SELECT. Si el SELECT retorna les files que esperaves, la modificació també les tocarà a elles.

-- Pas 1: comprovar l'abast
SELECT soci_id, nom, cognoms, email FROM socis WHERE soci_id = 19;
soci_id nom cognoms email
19 Pau Miralles (NULL)
-- Pas 2: ara sí, modificar
UPDATE socis SET email = '[email protected]' WHERE soci_id = 19;
UPDATE 1

UPDATE 1 confirma que s'ha tocat exactament una fila. Si veus un número més gran de l'esperat, alguna cosa ha anat malament, i a PostgreSQL, si ets dins d'una transacció, encara hi ets a temps (lliçó 06-01).

-- Pas 3: desfer, perquè el joc de dades continuï com estava
UPDATE socis SET email = NULL WHERE soci_id = 19;

Actualitzar diverses columnes i fer servir el valor anterior

Un UPDATE pot calcular el valor nou a partir de l'actual:

SELECT prestec_id, recarrec FROM prestecs WHERE prestec_id = 8;   -- 4.20

UPDATE prestecs
SET recarrec = recarrec + 0.50
WHERE prestec_id = 8;
UPDATE 1
SELECT prestec_id, recarrec FROM prestecs WHERE prestec_id = 8;
prestec_id recarrec
8 4.70
-- Desfer
UPDATE prestecs SET recarrec = 4.20 WHERE prestec_id = 8;

Important: recarrec + 0.50 sobre un NULL dóna NULL. Si haguéssim executat aquest UPDATE sense WHERE, els quatre préstecs oberts haurien passat de NULL a… NULL, i els altres vuit haurien pujat de preu. Silenciosament.

Un cas realista amb dues instruccions

Quan la Marta Alsina retorna EJ-3081, cal tocar dues taules:

-- 1) Tancar el préstec
UPDATE prestecs
SET data_devolucio = '2026-08-01', recarrec = 0.00
WHERE prestec_id = 9;

-- 2) Alliberar l'exemplar
UPDATE exemplars
SET estat = 'disponible'
WHERE codi = 'EJ-3081';
UPDATE 1
UPDATE 1

Això planteja una pregunta incòmoda: què passa si la primera instrucció funciona i la segona falla? La base quedaria en un estat inconsistent: un préstec tancat i un exemplar marcat com a prestat. La resposta és la transacció, i és el contingut de la lliçó 06-01. De moment, desfem:

UPDATE prestecs SET data_devolucio = NULL, recarrec = NULL WHERE prestec_id = 9;
UPDATE exemplars SET estat = 'prestat' WHERE codi = 'EJ-3081';

El WHERE oblidat

-- CATASTRÒFIC: posa el mateix correu als deu socis
UPDATE socis SET email = '[email protected]';
UPDATE 10

I a més violaria uq_socis_email, així que en aquest cas concret la restricció ens salvaria. No sempre hi haurà una restricció que et salvi. Un UPDATE exemplars SET estat = 'baixa'; hauria donat de baixa els 40.000 exemplars de BiblioRed sense protestar.

Costums que eviten el desastre:

  1. Escriure el WHERE abans que el SET. Comença teclejant UPDATE taula WHERE ... i després torna a inserir-hi el SET. Sona estrany, funciona.
  2. Provar amb SELECT primer. Sempre.
  3. Treballar dins d'una transacció en les operacions delicades (06-01).
  4. A psql, activar \set ON_ERROR_STOP on als scripts perquè s'aturin al primer error.

  1. DELETE: eliminar files

DELETE FROM taula WHERE condició;

Crearem una fila d'usar i llençar per no espatllar el joc de dades:

-- Alta de prova
INSERT INTO socis (soci_id, nom, cognoms, email, data_alta, sucursal_id, actiu)
VALUES (99, 'Bruno', 'Temporal', '[email protected]', '2026-08-01', 1, TRUE);
INSERT 0 1
-- Pas 1: comprovar
SELECT soci_id, nom, cognoms FROM socis WHERE soci_id = 99;
soci_id nom cognoms
99 Bruno Temporal
-- Pas 2: esborrar
DELETE FROM socis WHERE soci_id = 99;
DELETE 1

A PostgreSQL pots fer servir RETURNING també aquí, per deixar constància del que s'ha endut per davant:

DELETE FROM socis WHERE soci_id = 99 RETURNING soci_id, nom, cognoms;

DELETE i la integritat referencial

Intenta esborrar un soci que té préstecs:

DELETE FROM socis WHERE soci_id = 14;
ERROR:  update or delete on table "socis" violates foreign key constraint
        "fk_prestecs_soci" on table "prestecs"
DETAIL:  Key (soci_id)=(14) is still referenced from table "prestecs".

El gestor t'està protegint. Si permetés l'esborrament, els tres préstecs de la Marta Alsina quedarien orfes: apuntant a un soci que ja no existeix. Aquest comportament —i les seves alternatives, com l'esborrament en cascada— és el tema íntegre de la lliçó 02-06.

A SQLite, si vas oblidar PRAGMA foreign_keys = ON, aquest DELETE funcionarà i et deixarà tres files òrfenes sense dir res. És exactament l'escenari que la lliçó 02-06 t'ensenyarà a detectar i netejar.

El WHERE oblidat, versió definitiva

DELETE FROM prestecs;
DELETE 12

Sense WHERE, DELETE buida la taula sencera, sense preguntar i sense paperera. Si et passa fora d'una transacció, l'única sortida és la còpia de seguretat (lliçó 06-04). Els quatre costums de l'apartat anterior valen aquí amb més raó encara.

  1. TRUNCATE: buidar una taula sencera

Quan el que vols de veritat és buidar una taula completa, existeix una instrucció específica:

TRUNCATE TABLE prestecs;
DELETE FROM taula TRUNCATE TABLE taula
Subllenguatge DML DDL
Admet WHERE No
Velocitat en taules grans Lenta: esborra fila a fila Gairebé instantània
Registra cada fila esborrada No
Reinicia el generador d'identificadors No Opcionalment (RESTART IDENTITY)
Es pot desfer amb ROLLBACK A PostgreSQL sí; en altres gestors no
-- Buidar i reiniciar el comptador, arrossegant les taules dependents
TRUNCATE TABLE prestecs, reserves RESTART IDENTITY;

SQLite no té TRUNCATE; fes servir DELETE FROM taula sense WHERE, que internament optimitza.

No executis cap TRUNCATE sobre el teu biblioredb: perdries el joc de dades que acabes de carregar. Si ja ho has fet, torna a executar l'script de l'apartat 2.

Errors Habituals i Consells

  • UPDATE o DELETE sense WHERE. L'error més car que es comet amb SQL. Prova sempre la condició amb un SELECT abans.
  • Escriure 'NULL' en lloc de NULL. Amb cometes és una cadena de text de quatre lletres; sense cometes, l'absència de valor. Una columna que barreja totes dues coses és una columna arruïnada.
  • Fer servir = NULL o <> NULL. Zero files, sense error. Sempre IS NULL / IS NOT NULL.
  • Oblidar que <> valor exclou els NULL. WHERE recarrec <> 0 no retorna els préstecs amb recàrrec nul. Si els vols, afegeix-hi OR recarrec IS NULL.
  • Barrejar AND i OR sense parèntesis. AND guanya. Posa-hi parèntesis sempre.
  • INSERT sense llista de columnes. Es trenca tan bon punt algú toca l'esquema, de vegades en silenci.
  • Inserir identificadors explícits i no resincronitzar la seqüència. Provoca errors de clau duplicada molt després, quan ja ningú no recorda la càrrega inicial.
  • LIMIT sense ORDER BY. El resultat no és reproduïble i la paginació pot repetir o ometre files.
  • Refiar-se que LIKE distingeix majúscules. A PostgreSQL sí, a SQLite (ASCII) no, a MySQL depèn de la col·lació. Si necessites certesa, LOWER(columna) LIKE ....
  • Consell: a psql, \pset null '(nul)' fa visibles els NULL i \x on mostra els resultats en vertical, ideal per a files amples.
  • Consell: guarda tot el SQL en fitxers (esquema_biblioredb.sql, dades_biblioredb.sql) i executa'ls amb \i fitxer.sql a psql o .read fitxer.sql a sqlite3. Poder reconstruir la base en deu segons et donarà llibertat per experimentar sense por.

Exercicis

Tots es resolen sobre una sola taula. Escriu la consulta abans de mirar la solució i compara els resultats.

Exercici 1: Consultes de catàleg

  1. Els títols i editorials dels llibres publicats abans del 2010, del més antic al més recent.
  2. Els llibres sense ISBN registrat.
  3. Les editorials diferents del fons, en ordre alfabètic.
  4. Els títols que contenen la paraula "mar" en qualsevol posició, sense distingir majúscules.
  5. Els codis dels exemplars de la sucursal 2 que no estiguin disponibles.
  6. Els tres llibres més recents del fons.

Exercici 2: Consultes sobre socis i préstecs

  1. Els socis donats d'alta el 2021 o el 2022, amb nom i cognoms en una sola columna anomenada soci.
  2. Els socis que no estan actius.
  3. Els préstecs retornats amb retard (la data real de devolució és posterior a la prevista), ordenats per dies de retard… o, si encara no saps calcular la diferència, simplement per data de préstec.
  4. Els préstecs oberts la devolució dels quals estava prevista abans del 10 d'agost de 2026.
  5. La segona pàgina d'un llistat de préstecs ordenat per data de préstec descendent, amb 5 préstecs per pàgina.
  6. Els préstecs el recàrrec dels quals no és zero, inclosos aquells en què el recàrrec encara no s'ha calculat.

Exercici 3: Modificació segura

Escriu les instruccions per a cada operació, precedides del SELECT de comprovació i seguides de la instrucció que desfà el canvi.

  1. L'exemplar EJ-3088 surt del taller: passa'l a disponible.
  2. Corregeix l'editorial del llibre 337: passa de "Edicions Marlia" a "Edicions Marlia SL".
  3. Dóna d'alta una sòcia nova, Berta Colomer ([email protected]), a la sucursal Sud, amb data d'avui (fes servir '2026-08-02'), activa, deixant que el gestor li assigni l'identificador. Després, esborra-la.
  4. La reserva 4 estava cancel·lada per error: reactiva-la.

Solucions

Solució 1

-- 1
SELECT titol, editorial, any_publicacio
FROM llibres
WHERE any_publicacio < 2010
ORDER BY any_publicacio ASC;
titol editorial any_publicacio
Memòria de l'Eixample (1904) Ajuntament de Vallmar 1904
Els pilars de la Terra Editorial Andana 1989
El mapa del temps Editorial Andana 2008
-- 2  (IS NULL, no = NULL!)
SELECT llibre_id, titol FROM llibres WHERE isbn IS NULL;
llibre_id titol
339 Memòria de l'Eixample (1904)
-- 3
SELECT DISTINCT editorial FROM llibres ORDER BY editorial;
editorial
Ajuntament de Vallmar
Edicions Marlia
Editorial Andana
Premsa Tècnica Nord
-- 4  (ILIKE a PostgreSQL; LOWER(titol) LIKE '%mar%' és la forma portable)
SELECT titol FROM llibres WHERE titol ILIKE '%mar%';
titol
El mapa del temps
La casa de les marees

Observa que "El mapa" hi entra per mapa i "les marees" per marees: LIKE busca subcadenes, no paraules senceres.

-- 5
SELECT codi, estat FROM exemplars
WHERE sucursal_id = 2 AND estat <> 'disponible';
codi estat
EJ-3081 prestat
EJ-3090 prestat
-- 6
SELECT titol, any_publicacio FROM llibres ORDER BY any_publicacio DESC LIMIT 3;
titol any_publicacio
Manual de jardineria urbana 2023
L'hivern dels ocells 2021
Àlgebra per a impacients 2019

Solució 2

-- 1
SELECT nom || ' ' || cognoms AS soci, data_alta
FROM socis
WHERE data_alta BETWEEN '2021-01-01' AND '2022-12-31'
ORDER BY data_alta;
soci data_alta
Marta Alsina 2021-03-08
Ivan Pereda 2021-09-19
Núria Bastos 2022-01-30
-- 2
SELECT soci_id, nom, cognoms FROM socis WHERE actiu = FALSE;
-- també val: WHERE NOT actiu
soci_id nom cognoms
13 Ramón Etxebarri
-- 3
SELECT prestec_id, data_prestec, data_devolucio_prevista, data_devolucio, recarrec
FROM prestecs
WHERE data_devolucio > data_devolucio_prevista
ORDER BY data_prestec;
prestec_id data_prestec data_devolucio_prevista data_devolucio recarrec
2 2026-03-05 2026-03-26 2026-04-02 1.40
5 2026-04-12 2026-05-03 2026-05-10 1.40
8 2026-05-19 2026-06-09 2026-06-30 4.20

Cal notar que la condició compara dues columnes de la mateixa fila, cosa perfectament legítima. I que els quatre préstecs oberts no hi apareixen: NULL > data és DESCONEGUT. A PostgreSQL, data_devolucio - data_devolucio_prevista donaria directament els dies de retard (7, 7 i 21).

-- 4
SELECT prestec_id, soci_id, data_devolucio_prevista
FROM prestecs
WHERE data_devolucio IS NULL
  AND data_devolucio_prevista < '2026-08-10'
ORDER BY data_devolucio_prevista;
prestec_id soci_id data_devolucio_prevista
9 14 2026-08-04
10 15 2026-08-08
-- 5  Pàgina 2 amb 5 per pàgina: OFFSET (2-1) * 5 = 5
SELECT prestec_id, data_prestec
FROM prestecs
ORDER BY data_prestec DESC, prestec_id DESC
LIMIT 5 OFFSET 5;
prestec_id data_prestec
7 2026-05-08
4 2026-04-06
3 2026-03-11
2 2026-03-05
1 2026-03-02

(El segon criteri prestec_id DESC garanteix que la paginació sigui estable encara que hi hagués dates repetides.)

-- 6  La clau: NULL no és "diferent de zero", és "no se sap"
SELECT prestec_id, recarrec
FROM prestecs
WHERE recarrec <> 0 OR recarrec IS NULL
ORDER BY prestec_id;
prestec_id recarrec
2 1.40
5 1.40
8 4.20
9 (NULL)
10 (NULL)
11 (NULL)
12 (NULL)

Sense l'OR recarrec IS NULL hauries obtingut només tres files i hauries perdut de vista els quatre préstecs en curs.

Solució 3

-- 1
SELECT exemplar_id, codi, estat FROM exemplars WHERE codi = 'EJ-3088';   -- reparacio
UPDATE exemplars SET estat = 'disponible' WHERE codi = 'EJ-3088';        -- UPDATE 1
UPDATE exemplars SET estat = 'reparacio'  WHERE codi = 'EJ-3088';        -- desfer

-- 2
SELECT llibre_id, titol, editorial FROM llibres WHERE llibre_id = 337;
UPDATE llibres SET editorial = 'Edicions Marlia SL' WHERE llibre_id = 337;   -- UPDATE 1
UPDATE llibres SET editorial = 'Edicions Marlia'    WHERE llibre_id = 337;   -- desfer

-- 3  Sense donar soci_id: el genera la columna IDENTITY (serà el 21 si
--    vas resincronitzar la seqüència a l'apartat 3; si no, donarà error de
--    clau duplicada, que és justament la lliçó d'aquell apartat).
INSERT INTO socis (nom, cognoms, email, data_alta, sucursal_id, actiu)
VALUES ('Berta', 'Colomer', '[email protected]', '2026-08-02', 3, TRUE)
RETURNING soci_id;
--  soci_id
-- ---------
--       21

SELECT soci_id, nom, cognoms FROM socis WHERE cognoms = 'Colomer';
DELETE FROM socis WHERE cognoms = 'Colomer';                                 -- DELETE 1

-- 4
SELECT reserva_id, estat FROM reserves WHERE reserva_id = 4;                 -- cancellada
UPDATE reserves SET estat = 'activa'     WHERE reserva_id = 4;               -- UPDATE 1
UPDATE reserves SET estat = 'cancellada' WHERE reserva_id = 4;               -- desfer

Comprovació final que tot ha quedat com estava:

SELECT COUNT(*) FROM socis;      -- 10
SELECT COUNT(*) FROM prestecs;   -- 12
SELECT estat FROM reserves WHERE reserva_id = 4;   -- cancellada

Conclusió

Aquesta lliçó ha portat BiblioRed d'un esquema buit a una base de dades viva i consultable:

  • INSERT amb llista de columnes explícita (sempre), en una o diverses files, amb NULL sense cometes i amb RETURNING a PostgreSQL per conèixer els identificadors generats. I la comprovació automàtica d'unicitat i de claus foranes que el full de càlcul mai no va tenir.
  • El joc de dades de BiblioRed: 4 sucursals, 8 autors, 10 socis, 9 llibres, 15 exemplars, 12 préstecs i 5 reserves, tot fictici i coherent. És el material de treball de la resta del curs.
  • L'efecte secundari d'inserir claus a mà i com resincronitzar el generador amb RESTART WITH o setval.
  • SELECT amb projecció de columnes, àlies AS (amb cometes dobles quan porten espais), concatenació amb || i DISTINCT aplicat a la combinació completa de columnes.
  • WHERE amb comparacions, AND/OR/NOT i la seva precedència, BETWEEN (extrems inclosos), IN i NOT IN (amb el seu parany amb NULL), LIKE/ILIKE amb % i _, i IS NULL, que és l'única manera de preguntar per l'absència.
  • ORDER BY amb diversos criteris, ASC/DESC, NULLS FIRST/NULLS LAST i la importància d'un criteri de desempat.
  • LIMIT/OFFSET per paginar, sempre amb ORDER BY.
  • UPDATE i DELETE amb la disciplina del SELECT previ, la lectura del nombre de files afectades i el respecte que imposa el WHERE oblidat; més TRUNCATE com a buidatge ràpid de nivell DDL.

Fins aquí, cada consulta ha mirat una sola taula, i això deixa preguntes sense resposta: no sabem quiEJ-3081, ni quin títol és el més prestat, ni quins socis no han agafat mai res. Les dades estan repartides a propòsit —aquesta és l'essència del model relacional— i ara toca recompondre-les.

A la lliçó 02-04, Consultes Multitaula: JOIN i Subconsultes, aprendràs a unir socis amb prestecs, prestecs amb exemplars, exemplars amb llibres i llibres amb autors, tot en una sola consulta; a fer servir LEFT JOIN per trobar el que no té correspondència; i a niar consultes dins de consultes. És on SQL comença a resultar realment potent.

© Copyright 2026. Tots els drets reservats