Skip to content

6. Approfondimento del linguaggio SQL

6.1. Introduction

In questo capitolo presentiamo

  • altre sintassi del comando SELECT che lo rendono un comando di consultazione molto potente, in particolare per consultare più tabelle contemporaneamente.
  • sintassi estese di comandi già studiati

Per illustrare i vari comandi, lavoreremo con le seguenti tabelle utilizzate per la gestione degli ordini in un sistema di distribuzione di libri PME:

6.1.1. la tabella CLIENTS

Memorizza le informazioni sui clienti del sistema PME:

 

Image

ID
numero che identifica in modo univoco il cliente - chiave primaria
NOM
nome del cliente
STATUT
I=Persona fisica, E=Azienda, A=Amministrazione
PRENOM
nome (nel caso di una persona fisica)
CONTACT
Cognome della persona di contatto presso il cliente (nel caso di un'azienda o di un ente pubblico)
RUE
Indirizzo del cliente - via
VILLE
città
CPOSTAL
codice postale
TELEPH
Telefono
DEPUIS
Da quando è cliente?
DEBITEUR
O (Sì) se il cliente ha un debito nei confronti dell'azienda e N (No) in caso contrario.

6.1.2. la tabella ARTICLES

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

Image

ISBN
numero che identifica in modo univoco un libro (ISBN = International Standard Book Number) - chiave primaria
TITRE
Titolo del libro
CODEDITEUR
Codice che identifica in modo univoco un editore
AUTEUR
Nome dell'autore
RESUME
Sinossi del libro
QTEANCOUR
Quantità venduta nell'anno
QTEANPREC
Quantità venduta l'anno precedente
DERNVENTE
Data dell'ultima vendita
QTERECUE
Quantità dell'ultima consegna
DERNLIV
Data dell'ultima consegna
PRIXVENTE
Prezzo di vendita
COUT
Costo di acquisto
MINCDE
Quantità minima da ordinare
MINSTOCK
Livello minimo di scorte
QTESTOCK
Quantità in magazzino

Il contenuto potrebbe essere il seguente:

Image

6.1.3. la tabella COMMANDES

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

Image

NOCMD
Numero che identifica in modo univoco un ordine - chiave primaria
IDCLI
Numero del cliente che effettua l'ordine - codice esterno - codice articolo CLIENTS(ID)
DATE_CMD
Data di inserimento dell'ordine
ANNULE
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 codici e le quantità dei libri ordinati. La sua struttura è la seguente:

Image

NOCMD
Numero dell'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 della tabella LIVRES
QTE
Quantità ordinata

Il suo contenuto potrebbe essere il seguente:

Image

Come si vede sopra, l’ordine n. 3 (NOCMD) riguarda tre libri. Ciò significa che il cliente ha ordinato tre libri contemporaneamente. I dati di questo cliente si trovano nella tabella [COMMANDES], dove si vede che l'ordine n. 3 è stato effettuato dal cliente n. 5. La tabella [CLIENTS] ci indica che il cliente n. 5 è la società NetLogos di Segré.

6.2. L’ordine SELECT

In questa sede intendiamo approfondire la nostra conoscenza dell’ordine SELECT presentando nuove sintassi relative ad esso.

6.2.1. Sintassi di una query su più tabelle

syntaxe
SELECT colonna1, colonna2, ...
FROM tabella1, tabella2, ..., tabellap
WHERE condition
ORDER BY ...
action
La novità in questo caso consiste nel fatto che le colonne colonna1, colonna2, ... provengono da diverse tabelle tabella1, tabella2, ... Se due tabelle hanno colonne con lo stesso nome, l'ambiguità viene risolta utilizzando la notazione tablei.colonnej. Il codice condition può riferirsi alle colonne di tabelle diverse.

Funzionamento

1
Viene generata la tabella cartesiana di table1, table2, ..., tablep. Se ni è il numero di righe di tablei, la tabella costruita avrà quindi n1*n2*...*np righe contenenti l’insieme delle colonne delle diverse tabelle.
2
A questa tabella viene applicata la condition della WHERE. Viene così generata una nuova tabella
3
Questa è ordinata secondo la modalità indicata in ORDER.
4
Vengono visualizzate le colonne richieste in base a SELECT.

Esempi

Si utilizzano le tabelle presentate in precedenza. Si desidera conoscere 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 si inserisce il nome di tutte le tabelle di cui si fanno riferimento alle colonne. Nell'esempio precedente, le colonne selezionate appartengono tutte alla tabella DETAILS. Tuttavia, la condizione fa riferimento alla tabella COMMANDES. Da qui la necessità di specificare quest’ultima dopo FROM. L’operazione che verifica l’uguaglianza delle colonne di due tabelle diverse viene spesso definita «equi-join».

La query SELECT avrebbe potuto essere scritta anche nel modo seguente:

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. Si desidera ottenere lo stesso risultato di prima, ma con il titolo del libro ordinato, anziché il suo numero 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

Si ottiene lo stesso risultato con la seguente query SQL, 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'

Nell'esempio sopra riportato, vengono effettuati due join interni con la tabella [DETAILS]:

  • una con la tabella [COMMANDES] per accedere alla data di ordinazione di un libro
  • una con la tabella [ARTICLES] per accedere al titolo del libro ordinato

Si desidera inoltre il nome del cliente che effettua 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

Si desiderano inoltre le date degli ordini e la visualizzazione di tali date 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 nelle unioni:

  1. Dopo SELECT, si inseriscono le colonne che si desidera visualizzare. Se la colonna è presente in diverse tabelle, la si precede con il nome della tabella.
  2. Dopo FROM, si inseriscono tutte le tabelle che saranno analizzate da SELECT, ovvero le tabelle che contengono le colonne che si trovano dopo SELECT e WHERE.

6.2.2. L'auto-join

Vogliamo conoscere i libri che hanno un prezzo di vendita 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 dell'unione sono qui identiche: la tabella articles. Per distinguerle, viene loro assegnato un alias: from articles a, articles b. L'alias della prima tabella è a e quello della seconda è b. Questa sintassi può essere utilizzata anche se le tabelle sono diverse. Quando si utilizza un alias, questo deve essere impiegato ovunque nel comando SELECT al posto della tabella a cui si riferisce.

6.2.3. Join esterno

Si desidera conoscere i clienti che hanno effettuato un acquisto a settembre, indicando la data dell’ordine. Gli altri clienti vengono visualizzati senza tale 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

Ci sorprende non ottenere il risultato corretto. Dovrebbero essere presenti tutti i clienti nella tabella [CLIENTS], ma non è così. Se si riflette sul funzionamento del join esterno, ci si rende conto che i clienti che non hanno effettuato acquisti sono stati associati a una riga vuota della tabella COMMANDES e quindi a una data vuota (valore NULL nella terminologia SQL). Questa data non soddisfa quindi la condizione impostata sulla data e 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. Richieste annidate

syntaxe
SELECT colonna[s] FROM tabella[s]
WHERE espressione operatore query
ORDER BY ...
fonctionnement
requête è un comando SELECT che restituisce un insieme di 0, 1 o più valori. Si ha quindi una condizione WHERE del tipo
espressione operatore (val1, val2, ..., vali)
expression e vali devono essere dello stesso tipo. Se la query restituisce un solo valore, si torna a una condizione del tipo
espressione operatore valore
che conosciamo bene. Se la query restituisce un elenco di valori, si potranno utilizzare i seguenti operatori:
IN
expression IN (val1, val2, ..., vali): vero se expression ha come valore uno degli elementi dell'elenco vali.
NOT IN
inverso di IN
ANY
deve essere preceduto da =, !=, >, >=, <, <=
expression >= ANY (val1, val2, .., valn): vero se expression è >= a uno dei valori vali dell'elenco
ALL
deve essere preceduto da =, !=, >, >=, <, <=
expression >= ALL (val1, val2, .., valn): vero se l'espressione è >= a tutti i valori vali dell'elenco
EXISTS 
condizione: vera se requête restituisce almeno una riga.

Esempi

Riprendiamo la domanda già risolta con un'equi-giunzione: visualizzare 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 di quella del join equi. Si effettua un primo filtro con un SELECT, poi un secondo sul risultato ottenuto. In questo modo è possibile effettuare diversi filtri in serie.

Si desidera individuare 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 sono i clienti che hanno ordinato i titoli risultanti 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

Spiegazioni

  1. si selezionano nella tabella DETAILS i codici ISBN presenti tra i libri il cui prezzo è superiore al prezzo medio dei libri.
  2. Nelle righe selezionate nella fase precedente non è presente il codice cliente IDCLI. Esso si trova nella tabella COMMANDES. Il collegamento tra le due tabelle avviene tramite il numero d'ordine NOCMD, da cui deriva l'equi-join COMMANDES.nocmd=DETAILS.nocmd.
  3. Uno stesso cliente potrebbe aver acquistato più volte uno dei libri in questione; in tal caso, il suo codice IDCLI risulterà presente più volte. Per evitare ciò, si inserisce la chiave DISTINCT dopo SELECT. In generale, DISTINCT elimina i duplicati nelle righe risultanti da un SELECT.
  4. Per ottenere il nome del cliente, dovremmo eseguire un’ulteriore join tra le tabelle COMMANDES e CLIENTS, come illustrato dalla query seguente.
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 modo diverso rispetto alla clausola WHERE: utilizzando la clausola HAVING in combinazione con la clausola GROUP e BY. La clausola HAVING filtra gruppi di righe.

Analogamente alla clausola WHERE, la sintassi


     HAVING expression opérateur requête 

è possibile, con il vincolo già illustrato secondo cui expression deve essere una delle espressioni expri della clausola


     GROUP BY expr1, expr2, ...

Esempi

Quali sono le quantità vendute per i libri con un prezzo superiore a 200F?

Visualizziamo innanzitutto 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

In modo forse più evidente si sarebbe 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. Richieste correlate

Nel caso delle query annidate, si ha una query madre (la query più esterna) e una query figlia (la query più interna). La query madre viene valutata solo dopo che la query figlia è stata valutata completamente.

Le query correlate hanno la stessa sintassi, con la seguente differenza: la query figlia esegue un join sulla tabella della query madre. In questo caso, l'insieme costituito dalla query madre e dalla query figlia viene valutato ripetutamente per ogni riga della tabella madre.

Esempio

Riprendiamo l’esempio in cui desideriamo ottenere 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 viene eseguita sulla tabella clients. La query figlia esegue un join tra le tabelle clients e commandes. Si tratta quindi di una query correlata. Per ogni riga della tabella clients, viene eseguita la query figlia: essa cerca il codice cliente id negli ordini effettuati dopo il 24 settembre. Se non lo trova (not exists), viene visualizzato il nome del cliente. Successivamente, si passa alla riga successiva della tabella clients.

6.2.6. Criteri di selezione per la scrittura del SELECT

Abbiamo visto, in diverse occasioni, che era possibile ottenere lo stesso risultato con diverse scritture del 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

Richieste annidate

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

dà lo stesso risultato.

Richieste correlate

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

dà lo stesso risultato.

Gli autori Christian MAREE e Guy LEDANT, nel loro libro «SQL, Introduzione, Programmazione e Padroneggiamento», propongono alcuni criteri di scelta:

Prestazioni

L’utente non sa come il SGBD «riesca» a trovare i risultati richiesti. È quindi solo attraverso l’esperienza che scoprirà che una determinata sintassi è più efficiente di un’altra. MAREE e LEDANT affermano, sulla base della loro esperienza, che le query correlate sembrano generalmente più lente rispetto alle query annidate o ai join.

Formulazione

La formulazione tramite query annidate è spesso più leggibile e intuitiva rispetto al join. Tuttavia, non è sempre utilizzabile. In particolare, occorre tenere presenti due aspetti:

  • Le tabelle a cui appartengono le colonne utilizzate come argomenti nella query SELECT (SELECT col1, col2, ...) devono essere specificate dopo la parola chiave FROM. Viene quindi eseguito il prodotto cartesiano di queste tabelle, ovvero ciò che viene definito un join.
  • Quando la query visualizza risultati provenienti da una singola tabella e il filtraggio delle righe di quest’ultima richiede la consultazione di un’altra tabella, è possibile utilizzare le query annidate.

6.3. Estensioni della sintassi

Per motivi di praticità, abbiamo spesso presentato sintassi ridotte dei vari comandi. In questa sezione ne presentiamo le sintassi estese. Sono intuitive poiché analoghe a quelle del comando SELECT, ampiamente studiato.

INSERT

syntaxe1
INSERT INTO table (col1, col2, ..) VALUES (val1, val2, ...)
syntaxe2
INSERT INTO table (col1, col2, ..) (requête)
explication
Sono state presentate queste due sintassi

DELETE

syntaxe1
DELETE FROM table WHERE condition
explication
Questa sintassi è nota. Aggiungiamo che la condizione può contenere una query con la sintassi WHERE espressione operatore (query)

UPDATE

syntaxe1
UPDATE table
SET col1=expr1, col2=expr2, ...
WHERE condition
explication
Questa sintassi è già stata illustrata. Aggiungiamo che la condizione può contenere una query con la sintassi WHERE espressione operatore (query)
syntaxe2
UPDATE table
SET (col1, col2, ..) = query1, (cola, colb, ..) = query2, ...
WHERE condition
explication
I valori assegnati alle diverse colonne possono provenire da una query.