Skip to content

Bookshops, books and availability

SQL exercises · Part I

Problem

A database of books (each of a genre), bookshops, and the availability of each book in each bookshop, with the quantity in stock. Write four queries.

Schema and test data

CREATE TABLE Libri (
   codice INT PRIMARY KEY,
   titolo TEXT NOT NULL,
   id_genere INT NOT NULL,
   prezzo_di_copertina DECIMAL(10, 2),
   FOREIGN KEY (id_genere) REFERENCES Generi(id)
);
CREATE TABLE Generi (
   id INT PRIMARY KEY,
   nome TEXT NOT NULL,
   descrizione TEXT
);

CREATE TABLE Librerie (
   id INT PRIMARY KEY,
   indirizzo TEXT NOT NULL,
   città TEXT NOT NULL
);

CREATE TABLE Disponibilita (
   libreria INT NOT NULL,
   libro INT NOT NULL,
   quantità INT NOT NULL,
   PRIMARY KEY (libreria, libro),
   FOREIGN KEY (libreria) REFERENCES Librerie(id),
   FOREIGN KEY (libro) REFERENCES Libri(codice)
);
-- Test data (not part of the exercise): a bookshop with two cheap books, an empty
-- bookshop, and quantities that make "number of titles" and "number of copies" differ.
INSERT INTO Generi VALUES (1, 'Romanzo', NULL), (2, 'Saggio', NULL), (3, 'Giallo', NULL);
INSERT INTO Libri VALUES (1, 'Il nome della rosa', 1, 14.50), (2, 'Sapiens', 2, 22.00),
                         (3, 'La donna della domenica', 3, 11.00), (4, 'Il Gattopardo', 1, 18.00);
INSERT INTO Librerie VALUES (1, 'Via Roma 1', 'Milano'), (2, 'Corso Italia 5', 'Milano'),
                            (3, 'Via Po 10', 'Torino'), (4, 'Piazza Dante 2', 'Napoli');   -- 4: empty
INSERT INTO Disponibilita VALUES
    (1, 1, 3), (1, 3, 2), (1, 2, 1),
    (2, 1, 10), (2, 4, 1),
    (3, 2, 4), (3, 3, 20), (3, 4, 2);

The schema is the one given in the exercise; the test data are mine.

Queries

Query 1 · Bookshops with a book under 20

ID, address and city of the bookshops that have at least one book with a cover price below 20, ordered by bookshop ID.

SELECT C1.id, C1.indirizzo, C1.città

FROM Librerie AS C1
JOIN Disponibilita AS C2
ON C1.id = C2.libreria
JOIN Libri AS C3
ON C2.libro = C3.codice

WHERE C3.prezzo_di_copertina < 20
id indirizzo città
1 Via Roma 1 Milano
1 Via Roma 1 Milano
2 Corso Italia 5 Milano
2 Corso Italia 5 Milano
3 Via Po 10 Torino
3 Via Po 10 Torino

Duplicates and order

A bookshop appears once for every cheap book it stocks: each of the three appears twice in the test data. SELECT DISTINCT fixes it, and the requested ORDER BY C1.id is missing.

Query 2 · Books above 12 available in Milan

Code, title, cover price and genre name of the books with a cover price above 12 available in the bookshops of Milan, ordered by book code.

SELECT DISTINCT C1.codice, C1.titolo, C1.prezzo_di_copertina, C4.nome

FROM Libri AS C1
JOIN Disponibilita AS C2
ON C1.codice = C2.libro
JOIN Librerie AS C3
ON C2.libreria = C3.id
JOIN Generi AS C4
ON C1.id_genere = C4.id

WHERE C1.prezzo_di_copertina > 12 AND C3.città = 'Milano'
codice titolo prezzo_di_copertina nome
1 Il nome della rosa 14.5 Romanzo
2 Sapiens 22 Saggio
4 Il Gattopardo 18 Romanzo

DISTINCT is used correctly here: a book stocked by two Milan bookshops would otherwise appear twice. The requested ORDER BY C1.codice is missing.

Query 3 · Books available in each bookshop

For each bookshop: ID, address, city and the number of books it has, showing 0 for a bookshop with none; ordered by ID.

SELECT C1.id, C1.indirizzo, C1.città, COUNT(C2.quantità) AS numero_libri

FROM Librerie AS C1
LEFT JOIN Disponibilita AS C2
ON C1.id = C2.libreria

GROUP BY C1.id, C1.indirizzo, C1.città
ORDER BY C1.id
id indirizzo città numero_libri
1 Via Roma 1 Milano 3
2 Corso Italia 5 Milano 2
3 Via Po 10 Torino 3
4 Piazza Dante 2 Napoli 0

Query 4 · The bookshop with the most books

ID, address and city of the bookshop (or bookshops) with the most books, ordered by ID.

CREATE VIEW quantita_libri_view AS

SELECT C1.id AS id_libreria, C1.indirizzo, C1.città, COUNT(C2.quantità) AS quantita_libri

FROM Librerie AS C1
JOIN Disponibilita AS C2
ON C1.id = C2.libreria

GROUP BY id_libreria, C1.indirizzo, C1.città;

SELECT id_libreria, indirizzo, città
FROM quantita_libri_view
WHERE quantita_libri = (SELECT MAX(quantita_libri) FROM quantita_libri_view)
id_libreria indirizzo città
1 Via Roma 1 Milano
3 Via Po 10 Torino

Titles or copies?

COUNT(C2.quantità) counts the titles a bookshop stocks (one row of Disponibilita per title). If "number of books" means the copies in stock, the count must be SUM(C2.quantità) (with COALESCE(..., 0) for the empty bookshop in Query 3). The two readings give different answers on the test data: by titles, the bookshops 1 and 3 tie with 3 each; by copies, bookshop 3 wins with 26 against 11 and 6.

Takeaways

  • A join from the "one" side to the "many" side repeats rows: DISTINCT when only the "one" side is wanted.
  • COUNT counts rows, SUM adds values: the question decides which one.