Skip to content

6. SQL avanzato

6.1. Introduzione

In questo capitolo presentiamo

  • ulteriori sintassi dell’istruzione SELECT che la rendono un comando di interrogazione molto potente, in particolare per interrogare più tabelle contemporaneamente.
  • sintassi estese dei comandi già trattati

Per illustrare i vari ordini, lavoreremo con le seguenti tabelle utilizzate per la gestione degli ordini in una piccola-media azienda di distribuzione di libri:

6.1.1. la tabella CLIENTI

Memorizza le informazioni relative ai clienti dell'azienda:

 

Image

ID
Un identificatore univoco per il cliente - chiave primaria
NOME
nome del cliente
STATUS
I=Privato, E=Azienda, A=Ente pubblico
NOME
nome per un privato
CONTATTO
Nome della persona di contatto presso la sede del cliente (nel caso di un'azienda o di un ente pubblico)
VIA
Indirizzo del cliente - via
CITTÀ
Città
CAP
CAP
Telefono
Telefono
DA
Da quando è cliente?
DEBITORE
S (Sì) se il cliente ha un debito nei confronti dell'azienda e N (No) in caso contrario.

6.1.2. La tabella ARTICOLI

Memorizza le informazioni relative ai prodotti venduti, in questo caso libri. La sua struttura è la seguente:

Image

ISBN
Un identificatore univoco per un libro (ISBN = International Standard Book Number) - chiave primaria
TITOLO
Titolo del libro
CODICE_EDITORE
Codice che identifica in modo univoco un editore
AUTORE
Nome dell'autore
SINTESI
Sommario del libro
Q.tà
Quantità venduta quest'anno
QTEANPREC
Quantità venduta l'anno precedente
ULTIMA VENDITA
Data dell'ultima vendita
QTERECUE
Quantità dell'ultima consegna
LASTDELIVERY
Data dell'ultima consegna
PREZZODIVENDITA
Prezzo di vendita
COSTO
Costo di acquisto
MINCDE
Quantità minima d'ordine
MINSTOCK
Livello minimo delle scorte
QTESTOCK
Quantità in magazzino

Il contenuto potrebbe essere il seguente:

Image

6.1.3. la tabella ORDERS

Memorizza le informazioni relative agli ordini effettuati dai clienti. La sua struttura è la seguente:

Image

NOCMD
Un identificatore univoco per un ordine - chiave primaria
CUSTID
ID cliente per questo ordine - chiave esterna - riferimento a CUSTOMERS(ID)
ORDER_DATE
Data di inserimento dell'ordine
CANCELLATO
O (Sì) se l'ordine è stato annullato e N (No) in caso contrario.

Image

6.1.4. La tabella DETAILS

Contiene i dettagli di un ordine, ovvero i titoli e le quantità dei libri ordinati. La sua struttura è la seguente:

Image

NOCMD
Numero d'ordine - chiave esterna che fa riferimento alla colonna NOCMD della tabella COMMANDES
ISBN
Numero del libro ordinato - chiave esterna che fa riferimento alla colonna ISBN nella tabella BOOKS
QTE
Quantità ordinata

Il contenuto potrebbe essere il seguente:

Image

Sopra, vediamo che l'ordine n. 3 (NOCMD) riguarda tre libri. Ciò significa che il cliente ha ordinato tre libri contemporaneamente. I dati relativi a questo cliente si trovano nella tabella [ORDERS], dove vediamo che l'ordine n. 3 è stato effettuato dal cliente n. 5. La tabella [CUSTOMERS] ci dice che il cliente n. 5 è la società NetLogos di Segré.

6.2. L'istruzione SELECT

In questa sezione, intendiamo approfondire la nostra comprensione dell'istruzione SELECT introducendo nuove sintassi.

6.2.1. Sintassi di una query su più tabelle

sintassi
SELECT colonna1, colonna2, ...
FROM tabella1, tabella2, ..., tabellap
WHERE condizione
ORDER BY ...
azione
La novità in questo caso è che le colonne column1, column2, ... provengono da più tabelle table1, table2, ... Se due tabelle hanno colonne con lo stesso nome, l'ambiguità viene risolta utilizzando la notazione tablei.columnj. La condizione può essere applicata a colonne provenienti da tabelle diverse.

Come funziona

1
Viene costruito il prodotto cartesiano delle tabelle table1, table2, ..., tablep. Se n_i è il numero di righe nella tabella table_i, la tabella risultante avrà n₁*n₂*...*n_p righe contenenti tutte le colonne delle diverse tabelle.
2
A questa tabella viene applicata la condizione WHERE. Viene così generata una nuova tabella
3
Questa tabella viene ordinata secondo il metodo specificato in ORDER.
4
Vengono visualizzate le colonne specificate dopo SELECT.

Esempi

Utilizziamo le tabelle presentate in precedenza. Vogliamo vedere i dettagli degli ordini effettuati dopo il 25 settembre:

SQL>select details.nocmd,isbn,qte from commandes,details
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd

Image

Si noti che dopo FROM, elenchiamo i nomi di tutte le tabelle alle cui colonne facciamo riferimento. Nell'esempio precedente, le colonne selezionate appartengono tutte alla tabella DETAILS. Tuttavia, la condizione fa riferimento alla tabella ORDERS. Da qui la necessità di elencare quest'ultima dopo FROM. L'operazione che verifica l'uguaglianza tra colonne in due tabelle diverse è spesso chiamata equijoin.

La query SELECT avrebbe potuto essere scritta anche come segue:

SQL> select details.nocmd,isbn,qte from commandes
    inner join details on details.nocmd=commandes.nocmd
    where commandes.datecmd>'25-sep-91'

Continuiamo con i nostri esempi. Vogliamo ottenere lo stesso risultato di prima, ma con il titolo del libro ordinato, anziché il suo codice ISBN:

SQL>select commandes.nocmd, articles.titre, details.qte
  from commandes,articles,details
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd
  and details.isbn=articles.isbn

Image

Lo stesso risultato si ottiene con la seguente query SQL, che è meno leggibile:

SQL> select details.nocmd,articles.titre,details.qte from details
    inner join commandes on details.nocmd=commandes.nocmd
    inner join articles on  details.isbn=articles.isbn
    where commandes.datecmd>'25-sep-91'

Sopra, vengono eseguiti due inner join con la tabella [DETAILS]:

  • uno con la tabella [ORDERS] per accedere alla data dell'ordine di un libro
  • uno con la tabella [ARTICLES] per accedere al titolo del libro ordinato

Vogliamo anche il nome del cliente che ha effettuato l'ordine:

SQL>select commandes.nocmd, articles.titre, qte ,clients.nom
  from commandes,details,articles,clients
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd
  and details.isbn=articles.isbn
  and commandes.idcli=clients.id

Image

Vogliamo anche le date degli ordini e che queste date vengano visualizzate in ordine decrescente:

SQL>select commandes.nocmd, commandes.datecmd, articles.titre, qte ,clients.nom
    from commandes,details,articles,clients
    where commandes.datecmd>'25-sep-91'
    and details.nocmd=commandes.nocmd
    and details.isbn=articles.isbn
    and commandes.idcli=clients.id
    order by commandes.datecmd descending

Image

Ecco alcune regole da seguire quando si creano i join:

  1. Dopo SELECT, elencare le colonne che si desidera visualizzare. Se la colonna esiste in più tabelle, precederla con il nome della tabella.
  2. Dopo FROM, elencare tutte le tabelle che saranno interrogate dall'istruzione SELECT, ovvero le tabelle contenenti le colonne elencate dopo SELECT e WHERE.

6.2.2. Auto-join

Vogliamo trovare i libri che hanno un prezzo al dettaglio superiore a quello del libro "Using SQL":

SQL>select a.titre from articles a, articles b
  where b.titre='Using SQL'
  and a.prixvente>b.prixvente

Image

Le due tabelle coinvolte nel join sono identiche in questo caso: entrambe sono la tabella «articles». Per distinguerle, assegniamo loro degli alias: «articles a» e «articles b». L’alias della prima tabella è «a», mentre quello della seconda è «b». Questa sintassi può essere utilizzata anche se le tabelle sono diverse. Quando si utilizza un alias, esso deve essere impiegato in tutta l’istruzione SELECT al posto della tabella a cui si riferisce.

6.2.3. Join esterno

Vogliamo identificare i clienti che hanno effettuato un acquisto a settembre, insieme alla data dell'ordine. Gli altri clienti vengono visualizzati senza questa data:

SQL>select clients.nom,commandes.datecmd from clients
    left outer join commandes  on clients.id=commandes.idcli
    where datecmd between '01-sep-91' and '30-sep-91'

Image

È sorprendente che in questo caso non otteniamo il risultato corretto. Dovremmo avere tutti i clienti presenti nella tabella [CLIENTS], ma non è così. Se pensiamo a come funziona un outer join, ci rendiamo conto che i clienti che non hanno effettuato alcun acquisto sono stati abbinati a una riga vuota nella tabella ORDERS e quindi a una data vuota (un valore NULL nella terminologia SQL). Questa data non soddisfa la condizione impostata per la data, quindi il cliente corrispondente non viene visualizzato. Proviamo qualcos'altro:

SQL>select clients.nom,commandes.datecmd from clients
    left outer join commandes  on clients.id=commandes.idcli
    where (commandes.datecmd between '01-sep-91' and '30-sep-91')
        or (commandes.datecmd is null)

Image

Questa volta otteniamo la risposta corretta alla nostra domanda.

6.2.4. Query annidate

sintassi
SELECT colonna[e] FROM tabella[e]
WHERE espressione operatore di query
ORDER BY ...
Come funziona
Una query è un'istruzione SELECT che restituisce un insieme di 0, 1 o più valori. Abbiamo quindi una condizione WHERE del tipo
espressione operatore (val1, val2, ..., vali)
L'espressione e vali devono essere dello stesso tipo. Se la query restituisce un singolo valore, ci riduciamo a una condizione del tipo
espressione operatore valore
che ci è familiare. Se la query restituisce un elenco di valori, possiamo utilizzare i seguenti operatori:
IN
espressione IN (val1, val2, ..., vali): vero se l'espressione restituisce uno degli elementi dell'elenco vali.
NOT IN
opposto di IN
ANY
deve essere preceduto da =, !=, >, >=, <, <=
espressione >= ANY (val1, val2, .., valn): vero se l'espressione è >= uno dei valori vali nell'elenco
ALL
deve essere preceduta da =, !=, >, >=, <, <=
espressione >= ALL (val1, val2, .., valn): vero se l'espressione è >= tutti i valori validi nell'elenco
EXISTS
query: vero se la query restituisce almeno una riga.

Esempi

Riprendiamo la domanda già risolta con un equijoin: visualizza i titoli con un prezzo di vendita superiore a quello del libro "Using SQL".

SQL>select titre from ARTICLES
    where prixvente > (select prixvente from ARTICLES where titre='Using SQL')

Image

Questa soluzione sembra più intuitiva rispetto all'equijoin. Eseguiamo un filtro iniziale con un SELECT, poi un secondo filtro sull'insieme risultante. In questo modo possiamo eseguire diversi filtri in sequenza.

Vogliamo trovare i titoli con un prezzo di vendita superiore al prezzo medio di vendita:

SQL> select titre from ARTICLES
    where prixvente > (select avg(prixvente) from ARTICLES)

Image

Quali clienti hanno ordinato i titoli restituiti dalla query precedente?

SQL>select distinct idcli from COMMANDES,DETAILS
    where DETAILS.isbn in
    (select isbn from ARTICLES where prixvente
        > (select avg(prixvente) from ARTICLES))
    and COMMANDES.nocmd=DETAILS.nocmd

Image

Spiegazione

  1. Selezioniamo dalla tabella DETAILS i codici ISBN presenti tra i libri con un prezzo superiore al prezzo medio dei libri.
  2. Nelle righe selezionate nel passaggio precedente, l'ID cliente (IDCLI) non è presente. Si trova nella tabella ORDERS. Il collegamento tra le due tabelle è stabilito tramite il numero d'ordine (NOCMD), da cui la condizione di join ORDERS.nocmd=DETAILS.nocmd.
  3. Un singolo cliente potrebbe aver acquistato uno dei libri in questione più volte, nel qual caso il suo codice IDCLI apparirebbe più volte. Per evitare ciò, inseriamo la parola chiave DISTINCT dopo SELECT. DISTINCT elimina generalmente i duplicati dalle righe restituite da una query SELECT.
  4. Per recuperare il nome del cliente, dovremmo eseguire un join aggiuntivo tra le tabelle ORDERS e CUSTOMERS, come mostrato nella seguente query.
SQL> select distinct CLIENTS.nom from COMMANDES,DETAILS,CLIENTS
    where DETAILS.isbn in
    (select isbn from ARTICLES where prixvente
        > (select avg(prixvente) from ARTICLES))
    and COMMANDES.nocmd=DETAILS.nocmd
    and COMMANDES.IDCLI=CLIENTS.ID

Image

Trova i clienti che non hanno effettuato ordini dal 24 settembre:

SQL>select nom from CLIENTS
    where clients.id not in
    (select distinct commandes.idcli from commandes where datecmd>='24-sep-91')

Image

Abbiamo visto che è possibile filtrare le righe in modi diversi dall'utilizzo della clausola WHERE: utilizzando la clausola HAVING in combinazione con la clausola GROUP BY. La clausola HAVING filtra gruppi di righe.

Proprio come con la clausola WHERE, la sintassi


     HAVING expression opérateur requête 

è possibile, con il vincolo già menzionato che l'espressione deve essere una delle espressioni expri presenti nella clausola


     GROUP BY expr1, expr2, ...

Esempi

Quali sono i dati di vendita dei libri con un prezzo superiore a 200F?

Per prima cosa, visualizziamo le quantità vendute per titolo:

SQL>select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    group by titre

Image

Ora filtriamo i titoli:

SQL> select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    group by titre
    having titre in (select titre from ARTICLES where prixvente>200)

Image

Forse in modo più ovvio, avremmo potuto scrivere:

SQL>select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    and ARTICLES.prixvente>200
    group by titre

Image

6.2.5. Query annidate

Nel caso delle query annidate, esiste una query padre (la query più esterna) e una query figlio (la query più interna). La query padre viene valutata solo dopo che la query figlio è stata completamente valutata.

Le query correlate hanno la stessa sintassi, con la seguente piccola differenza: la query figlia esegue un join sulla tabella della query padre. In questo caso, la coppia di query padre-figlia viene valutata ripetutamente per ogni riga della tabella padre.

Esempio

Riprendiamo l'esempio in cui vogliamo i nomi dei clienti che non hanno effettuato ordini dal 24 settembre:

SQL> 
select nom from clients
    where not exists
        (select idcli from commandes
            where datecmd>='24-sep-91'
                and commandes.idcli=clients.id)

Image

La query principale opera sulla tabella dei clienti. La query secondaria esegue un join tra le tabelle dei clienti e degli ordini. Si tratta quindi di una query correlata. Per ogni riga della tabella dei clienti, la query secondaria viene eseguita: cerca l'ID del cliente negli ordini effettuati dopo il 24 settembre. Se non ne trova nessuno (not exists), viene visualizzato il nome del cliente. Quindi, passa alla riga successiva della tabella dei clienti.

6.2.6. Criteri per la scrittura dell'istruzione SELECT

Abbiamo visto, in diverse occasioni, che è possibile ottenere lo stesso risultato utilizzando diverse istruzioni SELECT. Facciamo un esempio: visualizzare i clienti che hanno effettuato un ordine:

Join

SQL>
select distinct nom from clients,commandes
    where clients.id=commandes.idcli

Image

Query annidate

SQL> 
select nom from clients
    where id in (select idcli from commandes)

restituisce lo stesso risultato.

Query correlate

SQL>
select nom from clients
    where exists (select * from commandes where commandes.idcli=clients.id)

restituisce lo stesso risultato.

Gli autori Christian MAREE e Guy LEDANT, nel loro libro "SQL: Introduzione, Programmazione e Padronanza", suggeriscono alcuni criteri di selezione:

Prestazioni

L'utente non sa come il DBMS "riesca" a trovare i risultati richiesti. È quindi solo attraverso l'esperienza che scoprirà che una query è più efficiente di un'altra. MAREE e LEDANT affermano, sulla base della loro esperienza, che le query correlate sembrano generalmente più lente delle query annidate o dei join.

Formulazione

La formulazione che utilizza query annidate è spesso più leggibile e intuitiva rispetto ai join. Tuttavia, non è sempre utilizzabile. È opportuno sottolineare in particolare due punti:

  • Le tabelle contenenti le colonne specificate nella clausola SELECT (SELECT col1, col2, ...) devono essere elencate dopo la parola chiave FROM. Viene quindi eseguito il prodotto cartesiano di queste tabelle, noto come join.
  • Quando la query visualizza i risultati di una singola tabella e per filtrare le righe di tale tabella è necessario consultare un'altra tabella, è possibile utilizzare le query annidate.

6.3. Estensioni della sintassi

Per comodità, abbiamo presentato per lo più sintassi abbreviate per i vari comandi. In questa sezione, presentiamo le loro sintassi espanse. Sono intuitive perché sono analoghe a quelle del comando SELECT, ampiamente studiato.

INSERT

sintassi1
INSERT INTO tabella (col1, col2, ...) VALUES (val1, val2, ...)
sintassi2
INSERT INTO tabella (col1, col2, ..) (query)
spiegazione
Sono state presentate queste due sintassi

DELETE

sintassi1
DELETE FROM tabella WHERE condizione
spiegazione
Questa sintassi è ben nota. Si noti che la condizione può contenere una query che utilizza la sintassi WHERE espressione operatore (query)

AGGIORNA

sintassi1
AGGIORNA tabella
SET col1=espr1, col2=espr2, ...
WHERE condizione
spiegazione
Questa sintassi è già stata presentata. Si noti che la condizione può contenere una query che utilizza la sintassi WHERE espressione operatore (query)
sintassi2
AGGIORNA tabella
SET (col1, col2, ..) = query1, (cola, colb, ..) = query2, ...
WHERE condizione
spiegazione
I valori assegnati alle varie colonne possono provenire da una query.