Salta al contenuto
Note per Studenti Esercizio - Interrogazioni SQL sul database dei riferimenti bibliografici (slide di SQL)

Esercizio - Interrogazioni SQL sul database dei riferimenti bibliografici (slide di SQL)

In questa pagina 8

Testo (le 25 interrogazioni di esempio delle slide di SQL del corso di Basi di Dati, Ing. Informatica UniPD, sul database dei riferimenti bibliografici). Per ognuna: la richiesta, l'espressione di algebra relazionale (dove la slide la riporta), l'SQL e il risultato sui dati del corso (la base di dati bibliographicreferences, 9 autori, 4 editori, 9 riferimenti). Tutte le query sono state eseguite con SQLite sugli stessi dati.

Teoria: SQL - interrogazioni, join e aggregazioniUn'interrogazione SQL ha almeno SELECT e FROM; le altre clausole (WHERE, GROUP BY, HAVING, ORDER BY) sono opzionali e HAVING richiede GROUP BY. Ordine logico di valutazione: FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY. SELECT corrisponde a π (ma senza DISTINCT non elimina i duplicati), WHERE a σ, gli alias ad AS/ρ, i join a ⋈ (INNER, LEFT, RIGHT, FULL). WHERE seleziona le righe prima del raggruppamento, HAVING seleziona i gruppi dopo. Le funzioni COUNT, SUM, AVG, MIN, MAX ignorano i NULL (COUNT(*) conta le righe, DISTINCT toglie i duplicati dentro la funzione); con un aggregato o con GROUP BY nella SELECT possono comparire solo colonne di raggruppamento o aggregati. LIKE usa % (qualsiasi sequenza) e _ (un carattere). I confronti con NULL non sono veri: serve IS NULL. Un filtro sulla tabella di destra di un LEFT JOIN va nella clausola ON, non in WHERE, altrimenti il join diventa interno. Con interi COUNT(a)/COUNT(b) è una divisione intera: serve CAST.SQL - interrogazioni, join e aggregazioni →, SQL - query nidificate, operatori insiemistici e visteUna sottoquery è una SELECT tra parentesi dentro un'altra: nella FROM (tabella derivata, con alias obbligatorio in PostgreSQL) oppure nella WHERE, come valore scalare con =, <, ..., come insieme con IN / NOT IN, ANY, ALL, oppure con EXISTS / NOT EXISTS. Una sottoquery correlata usa colonne della query esterna e va rivalutata per ogni riga. Le funzioni di aggregazione non si possono usare in WHERE: il massimo si ottiene con una sottoquery scalare (WHERE n = (SELECT MAX(n) ...)), con una vista o una CTE. NOT IN con una sottoquery che contiene NULL non restituisce mai righe: meglio NOT EXISTS, EXCEPT o LEFT JOIN ... IS NULL. "Per tutti" (divisione) = doppio NOT EXISTS o HAVING COUNT(DISTINCT ...) = numero totale. Gli operatori insiemistici UNION, INTERSECT, EXCEPT richiedono query compatibili e di default eliminano i duplicati (ALL li mantiene). Una vista è una tabella virtuale definita da una query (CREATE VIEW, DROP VIEW): semplifica le query, riusa sottoquery, nasconde i dati; la vista materializzata invece memorizza il risultato.SQL - query nidificate, operatori insiemistici e viste →; schema e DDL in SQL - definizione dei dati e vincoliSQL (standard ISO 9075) è un linguaggio dichiarativo con una parte di definizione dei dati (DDL: CREATE/DROP/ALTER di database, schemi, tipi enumerati, domini, tabelle) e una di manipolazione (DML: INSERT, DELETE, UPDATE, SELECT). Una tabella SQL è un multi-insieme di righe (può avere duplicati), quindi non è una relazione in senso stretto. CREATE TABLE elenca colonne con tipo e vincoli: NOT NULL, DEFAULT, CHECK (di colonna o di tupla), PRIMARY KEY (una sola, anche composta), UNIQUE, FOREIGN KEY ... REFERENCES con ON DELETE/ON UPDATE (NO ACTION, CASCADE, SET NULL, SET DEFAULT). I tipi principali sono CHAR/VARCHAR, NUMERIC, INTEGER, SERIAL, BOOLEAN, DATE, TIME, TIMESTAMP, più UUID, intervalli e JSON in PostgreSQL. DROP ... CASCADE cancella anche gli oggetti dipendenti, RESTRICT (predefinito) rifiuta se ce ne sono. DELETE e UPDATE senza WHERE agiscono su tutta la tabella.SQL - definizione dei dati e vincoli →; algebra in Algebra relazionale - operatori di base e joinLe operazioni sul modello relazionale sono di aggiornamento (inserimento, modifica, cancellazione, che possono violare i vincoli di integrità: si rifiuta, si corregge, si propaga con cascade o si imposta NULL/default) e di interrogazione (funzioni che da un'istanza del database producono una relazione). L'algebra relazionale è il linguaggio procedurale delle interrogazioni: gli operatori primitivi sono unione, differenza, prodotto, rinomina ρ, selezione σ e proiezione π; derivati sono intersezione, theta-join, equi-join, join naturale. Unione, intersezione e differenza richiedono relazioni compatibili all'unione (stessi attributi e domini). La selezione è una decomposizione orizzontale, la proiezione verticale (commutano solo se gli attributi della selezione sono nella proiezione). Il theta-join è σ_φ(R1×R2), il join naturale equivale a un equi-join sugli attributi omonimi seguito da una proiezione; la sua cardinalità è tra 0 e |R1||R2|, ≤|R1| se gli attributi comuni sono chiave di R2, =|R1| se sono anche chiave esterna di R1 con vincolo di integrità referenziale. Le tuple senza corrispondente sono "dangling". I valori NULL richiedono condizioni esplicite IS NULL / IS NOT NULL, perché σ_{A>40} ∪ σ_{A≤40} non restituisce le tuple con A nullo.Algebra relazionale - operatori di base e join →.

Schema e dati

Author(id, name, surname), Publisher(id, name), Reference(id, title, year, publisher) con publisher →\to Publisher, Information(reference, notes) con reference →\to Reference, Write(reference, author, authorNumber) con chiavi esterne verso Reference e Author.

Author: id name surname
BuckJR John R. Buck
CastelliD Donatella Castelli
CornellG Gary Cornell
GilardiG Gianni Gilardi
HorstmannCS Cay S. Horstmann
OppenheimAV Alan V. Oppenheim
PaganoP Pasquale Pagano
SchaferRW Ronald W. Schafer
TanenbaumAS Andrew S. Tanenbaum
Publisher: id name
ECDL2002 Proc. Sixth European Conference on Research and Advanced Technology for Digital Libraries (ECDL 2002)
McGrawHill McGraw-Hill
PrenticeHall Prentice Hall International Editions
Springer Springer
Reference: id title year publisher
CastelliPagano2002a OpenDLib: a Digital Library Service System 2002 ECDL2002
CastelliPagano2002b Foundations of a Multidimensional Query Language for Digital Libraries 2002 ECDL2002
ECDL2002 Proc. 6th European Conference on Research and Advanced Technology for Digital Libraries (ECDL 2002) 2002 Springer
Gilardi1991 Analisi Uno 1991 McGrawHill
HorstmannCornell2000 Java 2 tecniche avanzate 2000 McGrawHill
HorstmannCornell2001 Java 2 i fondamenti 2001 McGrawHill
OppenheimSchafer1999 Discrete-Time Signal Processing 1999 PrenticeHall
Tanenbaum1996 Computer Networks 1996 PrenticeHall
Tanenbaum2001 Modern Operating Systems 2001 PrenticeHall

Write (reference, author, authorNumber): CastelliPagano2002a: CastelliD 1, PaganoP 2; CastelliPagano2002b: CastelliD 1, PaganoP 2; Gilardi1991: GilardiG 1; HorstmannCornell2000: HorstmannCS 1, CornellG 2; HorstmannCornell2001: HorstmannCS 1, CornellG 2; OppenheimSchafer1999: OppenheimAV 1, SchaferRW 2, BuckJR 3; Tanenbaum1996 e Tanenbaum2001: TanenbaumAS 1. Information (reference, notes): CastelliPagano2002a "pp. 292 - 308", CastelliPagano2002b "pp. 251 - 265", Tanenbaum1996 "3rd edition", Tanenbaum2001 "2nd edition".


Proiezione, selezione, espressioni (1-7)

1. Nome e cognome di tutti gli autori. πName,Surname(Author)\pi_{\text{Name},\text{Surname}}(\text{Author}).

sql
SELECT name, surname FROM Author;

9 righe, una per autore (la chiave è id, quindi nessun duplicato anche senza DISTINCT).

2. Tutti gli editori. SELECT * FROM Publisher;: 4 righe (* = tutte le colonne).

3. Gli autori e il loro numero d'ordine. πauthor,authorNumber(Write)\pi_{\text{author},\text{authorNumber}}(\text{Write}).

sql
SELECT DISTINCT author, authorNumber FROM Write;

9 coppie distinte. Il DISTINCT rende la query equivalente alla proiezione algebrica (in SQL senza DISTINCT i duplicati restano; qui non ce ne sono comunque).

4. Cognome e nome in una colonna.

sql
SELECT surname || ' ' || name AS AuthorData FROM Author;

Valori come Buck John R., Castelli Donatella, Tanenbaum Andrew S. (9 righe). || concatena, AS dà il nome alla colonna.

5. Il nome di Gilardi. πName(σSurname=′Gilardi′(Author))\pi_{\text{Name}}(\sigma_{\text{Surname}='\text{Gilardi}'}(\text{Author})).

sql
SELECT name FROM Author WHERE surname = 'Gilardi';

Risultato: Gianni.

6. Identificatore e titolo dei riferimenti del 1999 o del 2000, in ordine di titolo.

sql
SELECT id, title FROM Reference WHERE year = 1999 OR year = 2000 ORDER BY title ASC;

OppenheimSchafer1999 (Discrete-Time Signal Processing) e HorstmannCornell2000 (Java 2 tecniche avanzate), in ordine alfabetico di titolo.

7. Gli autori il cui cognome inizia per C.

sql
SELECT name, surname FROM Author WHERE surname LIKE 'C%';

Castelli e Cornell. % = sequenza qualsiasi; _ = un solo carattere.

Aggregazioni e raggruppamenti (8-15)

8. Il numero d'autore massimo.

sql
SELECT MAX(authorNumber) AS MaxNumberAuthors FROM Write;

Risultato: 3 (il terzo autore di Oppenheim-Schafer: Buck). La variante SELECT reference, MAX(authorNumber) FROM Write è errata: con un aggregato la SELECT non può contenere colonne non aggregate (PostgreSQL la rifiuta; SQLite la accetta e restituisce OppenheimSchafer1999, quasi per caso).

9. Quante righe ha Author. SELECT COUNT(*) AS AuthorRowsNumber FROM Author; ⇒\Rightarrow 9.

10. Quanti autori diversi compaiono in Write.

sql
SELECT COUNT(DISTINCT author) AS WriteAuthorsNumber FROM Write;

9: tutti gli autori hanno scritto qualcosa (COUNT(author) darebbe 14, le righe di Write).

11. Gli autori che compaiono in Write (con GROUP BY senza aggregati).

sql
SELECT author FROM Write GROUP BY author;

9 righe: equivale a SELECT DISTINCT author FROM Write.

12. Per ogni autore il numero di riferimenti.

sql
SELECT author, COUNT(*) FROM Write GROUP BY author;

BuckJR 1, CastelliD 2, CornellG 2, GilardiG 1, HorstmannCS 2, OppenheimAV 1, PaganoP 2, SchaferRW 1, TanenbaumAS 2.

13. Gli autori con più di un riferimento (HAVING).

sql
SELECT author, COUNT(*) FROM Write GROUP BY author HAVING COUNT(*) > 1;

CastelliD, CornellG, HorstmannCS, PaganoP, TanenbaumAS (tutti con 2).

14. Come la 12, ma contando solo le righe con numero d'autore maggiore di 1 (WHERE).

sql
SELECT author, COUNT(*) FROM Write WHERE authorNumber > 1 GROUP BY author;

BuckJR 1, CornellG 2, PaganoP 2, SchaferRW 1. Il WHERE agisce prima del raggruppamento: gli autori che sono sempre primi (Castelli, Gilardi, ...) scompaiono.

15. Come la 14, tenendo solo i gruppi con più di una riga (WHERE e HAVING).

sql
SELECT author, COUNT(*) FROM Write WHERE authorNumber > 1 GROUP BY author HAVING COUNT(*) > 1;

CornellG 2, PaganoP 2. Prima WHERE (righe), poi GROUP BY, poi HAVING (gruppi).

Join (16-21)

16. Titolo dei riferimenti dell'editore McGraw-Hill. πname,title(Reference⋈publisher=idσname=′McGraw-Hill′(Publisher))\pi_{\text{name},\text{title}}\big(\text{Reference}\bowtie_{\text{publisher}=\text{id}}\sigma_{\text{name}='\text{McGraw-Hill}'}(\text{Publisher})\big).

sql
SELECT name, title
FROM Reference AS R INNER JOIN Publisher AS P ON R.publisher = P.id
WHERE name = 'McGraw-Hill';

3 righe: Analisi Uno, Java 2 tecniche avanzate, Java 2 i fondamenti.

17. Quanti riferimenti per editore.

sql
SELECT name, COUNT(*)
FROM Reference AS R INNER JOIN Publisher AS P ON R.publisher = P.id
GROUP BY name;

McGraw-Hill 3, Prentice Hall 3, ECDL 2, Springer 1.

18. Cognome degli autori e titolo dei loro riferimenti. πsurname,title(Author⋈id=authorWrite⋈reference=idReference)\pi_{\text{surname},\text{title}}(\text{Author}\bowtie_{\text{id}=\text{author}}\text{Write}\bowtie_{\text{reference}=\text{id}}\text{Reference}) (con la rinomina degli id).

sql
SELECT surname, title
FROM Author AS A INNER JOIN Write AS W ON A.id = W.author
                 INNER JOIN Reference AS R ON W.reference = R.id;

14 righe, una per riga di Write.

19. Titolo e note di ogni riferimento (anche senza note). πtitle,notes(Reference ⋈id=referenceL Information)\pi_{\text{title},\text{notes}}(\text{Reference}\ \bowtie^{\text{L}}_{\text{id}=\text{reference}}\ \text{Information}).

sql
SELECT title, notes
FROM Reference AS R LEFT JOIN Information AS I ON R.id = I.reference;

9 righe: quattro con note (pp. 292-308, pp. 251-265, 3rd edition, 2nd edition) e cinque con NULL. Un join interno ne darebbe solo 4.

20. Quanti riferimenti non hanno informazioni aggiuntive.

sql
SELECT COUNT(*)
FROM Reference AS R LEFT JOIN Information AS I ON R.id = I.reference
WHERE notes IS NULL;

5: il left join aggiunge i riferimenti senza note con notes = NULL, WHERE ... IS NULL li isola. (Dopo il join esterno il filtro sul lato destro è proprio quello che si vuole: tiene i NULL.)

21. Per ogni editore e cognome d'autore, il numero di riferimenti, ordinato.

sql
SELECT P.name, A.surname, COUNT(R.id) AS ReferencesNumber
FROM Reference AS R INNER JOIN Publisher AS P ON R.publisher = P.id
     INNER JOIN Write AS W ON R.id = W.reference
     INNER JOIN Author AS A ON W.author = A.id
GROUP BY P.name, A.surname
ORDER BY P.name ASC, ReferencesNumber DESC, A.surname ASC;

9 righe: per esempio McGraw-Hill: Cornell 2, Horstmann 2, Gilardi 1; Prentice Hall: Tanenbaum 2, Buck 1, Oppenheim 1, Schafer 1; ECDL: Castelli 2, Pagano 2. Osservazione: si raggruppa per cognome, non per A.id: se due autori diversi avessero lo stesso cognome sarebbero fusi in un gruppo.

Query nidificate (22-23)

22. Il massimo numero di riferimenti di un editore.

sql
SELECT MAX(ReferencesNumber)
FROM (SELECT publisher, COUNT(id) AS ReferencesNumber
      FROM Reference GROUP BY publisher) AS Counter;

Risultato 3. Non esiste MAX(COUNT(...)): il conteggio per editore è una tabella derivata, e il massimo si calcola su quella.

23. L'editore (o gli editori) con il massimo numero di riferimenti. La slide procede per tentativi; gli errori intermedi sono istruttivi:

sql
-- Tentativo 1: ERRATO, un aggregato non può stare in WHERE
SELECT publisher, ReferencesNumber
FROM (SELECT publisher, COUNT(id) AS ReferencesNumber FROM Reference GROUP BY publisher) AS Counter
WHERE ReferencesNumber = MAX(ReferencesNumber);

-- Tentativo 2: ERRATO, la sottoquery fa riferimento a "Counter", visibile solo nella query esterna
WHERE ReferencesNumber = (SELECT MAX(ReferencesNumber) FROM Counter);

-- Soluzione: si ripete la tabella derivata dentro la sottoquery
SELECT publisher, ReferencesNumber
FROM (SELECT publisher, COUNT(id) AS ReferencesNumber FROM Reference GROUP BY publisher) AS Counter
WHERE ReferencesNumber = (SELECT MAX(ReferencesNumber)
                          FROM (SELECT publisher, COUNT(id) AS ReferencesNumber
                                FROM Reference GROUP BY publisher) AS Counter2);

Risultato: McGrawHill 3 e PrenticeHall 3 (a pari merito). La ripetizione si evita con una vista (si vede sotto) o con una CTE.

Con una vista (stesso risultato, provato):

sql
CREATE VIEW PublisherReferences AS
SELECT publisher, COUNT(id) AS ReferencesNumber FROM Reference GROUP BY publisher;

SELECT publisher, ReferencesNumber FROM PublisherReferences
WHERE ReferencesNumber = (SELECT MAX(ReferencesNumber) FROM PublisherReferences);

Divisione intera e insiemi (24-25)

24. Il numero medio di riferimenti per editore.

sql
SELECT COUNT(id) / COUNT(DISTINCT publisher) FROM Reference;                      -- 9 / 4 = 2 (divisione intera)
SELECT CAST(COUNT(id) AS DOUBLE PRECISION) / COUNT(DISTINCT publisher) FROM Reference;   -- 2,25

Con due interi / è la divisione intera e dà 2. Si converte uno dei due operandi per ottenere 2,25 (in SQLite il tipo si chiama REAL).

25. I riferimenti senza informazioni aggiuntive (differenza). πid(Reference)−ρid←reference(πreference(Information))\pi_{\text{id}}(\text{Reference})-\rho_{\text{id}\leftarrow\text{reference}}(\pi_{\text{reference}}(\text{Information})).

sql
SELECT id FROM Reference
EXCEPT
SELECT reference FROM Information;

5 righe: ECDL2002, Gilardi1991, HorstmannCornell2000, HorstmannCornell2001, OppenheimSchafer1999 (le stesse della query 20).


Esercizi aggiuntivi (stesso database)

A. Gli editori senza riferimenti. Dopo aver aggiunto un editore Wiley, le quattro forme NOT IN, NOT EXISTS, LEFT JOIN ... IS NULL, EXCEPT restituiscono Wiley. Se un riferimento ha publisher = NULL, la forma NOT IN (SELECT publisher FROM Reference) restituisce zero righe (verificato): si usa NOT EXISTS oppure si aggiunge WHERE publisher IS NOT NULL nella sottoquery.

B. I riferimenti più recenti della media del proprio editore (sottoquery correlata).

sql
SELECT r.id, r.year, r.publisher FROM Reference r
WHERE r.year > (SELECT AVG(r2.year) FROM Reference r2 WHERE r2.publisher = r.publisher);

4 righe: HorstmannCornell2000, HorstmannCornell2001, OppenheimSchafer1999, Tanenbaum2001.

C. I cognomi degli autori che non sono mai oltre il primo posto (NOT EXISTS).

sql
SELECT surname FROM Author a
WHERE NOT EXISTS (SELECT * FROM Write w WHERE w.author = a.id AND w.authorNumber > 1);

Castelli, Gilardi, Horstmann, Oppenheim, Tanenbaum.

D. Il trigger sull'ordine degli autori. In SQL avanzato - indici, transazioni e triggerUn file di record può essere heap (inserimento in ordine, ricerca per uguaglianza in media n/2 accessi), ordinato (log2 n), hash (costante, solo uguaglianza) o B-tree (log_F n, anche intervalli). Un indice è una struttura ausiliaria di coppie (chiave K, puntatore P) ordinate, molto più piccola della tabella: l'indice primario è sul campo di ordinamento del file ordinato (una chiave per blocco, non denso, log2(m)+1 accessi), l'indice secondario è su un campo qualsiasi (denso). CREATE [UNIQUE] INDEX, anche su espressioni; PostgreSQL usa tabelle heap con indici secondari B-tree (predefinito) o hash, e PRIMARY KEY e UNIQUE creano un indice implicito. Gli indici velocizzano le letture ma costano nelle scritture e in spazio. Una transazione è una sequenza atomica di operazioni con proprietà ACID (atomicità, consistenza, isolamento, durabilità): START TRANSACTION, COMMIT, SAVEPOINT, ROLLBACK [TO SAVEPOINT]. Un trigger è una regola evento-condizione-azione (ECA): BEFORE/AFTER, ROW/STATEMENT, WHEN, con funzione PL/pgSQL che usa NEW e OLD; serve per vincoli complessi e attributi derivati.SQL avanzato - indici, transazioni e trigger → c'è il trigger che impone che il primo autore di un riferimento abbia numero 1 e che non ci siano due numeri uguali; provato in SQLite su un riferimento nuovo.

Errori nel materiale del corso

  • Nella slide sulla query 23, il tentativo con FROM Counter dentro la sottoquery è presentato come passaggio: non è valido perché Counter è un alias definito dalla FROM della query esterna.
  • Alcune slide di esempio mostrano le tabelle con nomi italiani (Riferimento, Autore) e le query con nomi inglesi (Reference, Author): è lo stesso schema in due lingue.

Versione ripasso

Schema: Author(id, name, surname), Publisher(id, name), Reference(id, title, year, publisher), Information(reference, notes), Write(reference, author, authorNumber). Teoria: SQL - interrogazioni, join e aggregazioniUn'interrogazione SQL ha almeno SELECT e FROM; le altre clausole (WHERE, GROUP BY, HAVING, ORDER BY) sono opzionali e HAVING richiede GROUP BY. Ordine logico di valutazione: FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY. SELECT corrisponde a π (ma senza DISTINCT non elimina i duplicati), WHERE a σ, gli alias ad AS/ρ, i join a ⋈ (INNER, LEFT, RIGHT, FULL). WHERE seleziona le righe prima del raggruppamento, HAVING seleziona i gruppi dopo. Le funzioni COUNT, SUM, AVG, MIN, MAX ignorano i NULL (COUNT(*) conta le righe, DISTINCT toglie i duplicati dentro la funzione); con un aggregato o con GROUP BY nella SELECT possono comparire solo colonne di raggruppamento o aggregati. LIKE usa % (qualsiasi sequenza) e _ (un carattere). I confronti con NULL non sono veri: serve IS NULL. Un filtro sulla tabella di destra di un LEFT JOIN va nella clausola ON, non in WHERE, altrimenti il join diventa interno. Con interi COUNT(a)/COUNT(b) è una divisione intera: serve CAST.SQL - interrogazioni, join e aggregazioni →, SQL - query nidificate, operatori insiemistici e visteUna sottoquery è una SELECT tra parentesi dentro un'altra: nella FROM (tabella derivata, con alias obbligatorio in PostgreSQL) oppure nella WHERE, come valore scalare con =, <, ..., come insieme con IN / NOT IN, ANY, ALL, oppure con EXISTS / NOT EXISTS. Una sottoquery correlata usa colonne della query esterna e va rivalutata per ogni riga. Le funzioni di aggregazione non si possono usare in WHERE: il massimo si ottiene con una sottoquery scalare (WHERE n = (SELECT MAX(n) ...)), con una vista o una CTE. NOT IN con una sottoquery che contiene NULL non restituisce mai righe: meglio NOT EXISTS, EXCEPT o LEFT JOIN ... IS NULL. "Per tutti" (divisione) = doppio NOT EXISTS o HAVING COUNT(DISTINCT ...) = numero totale. Gli operatori insiemistici UNION, INTERSECT, EXCEPT richiedono query compatibili e di default eliminano i duplicati (ALL li mantiene). Una vista è una tabella virtuale definita da una query (CREATE VIEW, DROP VIEW): semplifica le query, riusa sottoquery, nasconde i dati; la vista materializzata invece memorizza il risultato.SQL - query nidificate, operatori insiemistici e viste →.

  • Proiezione e selezione (1-7): SELECT name, surname FROM Author dà 9 righe; SELECT DISTINCT author, authorNumber FROM Write per la π\pi algebrica; surname || ' ' || name AS AuthorData concatena; WHERE surname = 'Gilardi' dà Gianni; year = 1999 OR year = 2000 ORDER BY title dà OppenheimSchafer1999 e HorstmannCornell2000; LIKE 'C%' dà Castelli e Cornell.
  • Aggregati (8-10): MAX(authorNumber) = 3; COUNT(*) su Author = 9; COUNT(DISTINCT author) su Write = 9. La variante SELECT reference, MAX(authorNumber) FROM Write è errata.
  • Raggruppamento (11-15): GROUP BY author dà 9 gruppi, con BuckJR 1, CastelliD 2, CornellG 2, GilardiG 1, HorstmannCS 2, OppenheimAV 1, PaganoP 2, SchaferRW 1, TanenbaumAS 2. HAVING COUNT(*) > 1 dà 5 autori. WHERE authorNumber > 1 prima del gruppo dà BuckJR 1, CornellG 2, PaganoP 2, SchaferRW 1; con anche HAVING restano CornellG e PaganoP.
sql
SELECT author, COUNT(*) FROM Write WHERE authorNumber > 1 GROUP BY author HAVING COUNT(*) > 1;
  • Join (16-21): McGraw-Hill ha 3 titoli. Riferimenti per editore: McGraw-Hill 3, Prentice Hall 3, ECDL 2, Springer 1. Autori-titoli: 14 righe. Left join con Information: 9 righe, di cui 5 con notes NULL; WHERE notes IS NULL dà 5. Raggruppare per cognome fonde gli omonimi.
sql
SELECT name, title FROM Reference AS R INNER JOIN Publisher AS P ON R.publisher = P.id
WHERE name = 'McGraw-Hill';
SELECT COUNT(*) FROM Reference AS R LEFT JOIN Information AS I ON R.id = I.reference
WHERE notes IS NULL;
  • Massimo (22-23): MAX su tabella derivata = 3. Con WHERE ReferencesNumber = MAX(...) la query è errata: un aggregato non va in WHERE. Con FROM Counter dentro la sottoquery è errata: l'alias è visibile solo fuori. Soluzione: ripetere la tabella derivata, oppure vista o CTE. Risultato: McGrawHill e PrenticeHall, 3 ciascuno.
sql
WITH Counter AS (SELECT publisher, COUNT(id) AS n FROM Reference GROUP BY publisher)
SELECT * FROM Counter WHERE n = (SELECT MAX(n) FROM Counter);
  • Cast e differenza (24-25): 9/4=29/4=2 con interi, 2,252{,}25 con CAST(... AS DOUBLE PRECISION). EXCEPT dà 5 riferimenti senza informazioni: ECDL2002, Gilardi1991, HorstmannCornell2000, HorstmannCornell2001, OppenheimSchafer1999.
sql
SELECT id FROM Reference EXCEPT SELECT reference FROM Information;
  • Extra: con un editore senza riferimenti NOT IN può dare zero righe se la sottoquery contiene NULL: si usa NOT EXISTS. Correlata: riferimenti più recenti della media del proprio editore, 4 righe. NOT EXISTS per gli autori mai oltre il primo posto: Castelli, Gilardi, Horstmann, Oppenheim, Tanenbaum.
sql
SELECT surname FROM Author a
WHERE NOT EXISTS (SELECT * FROM Write w WHERE w.author = a.id AND w.authorNumber > 1);
SELECT r.id, r.year, r.publisher FROM Reference r
WHERE r.year > (SELECT AVG(r2.year) FROM Reference r2 WHERE r2.publisher = r.publisher);

Errori tipici: aggregato in WHERE; colonne non aggregate con un aggregato; raggruppare per cognome; COUNT(*) con left join; divisione intera tra interi.

Teoria collegata