INDICE LINGUAGGIO SQL
1.01 - Database Relazionali
1.02 - Creare il database
1.03 - Popolare il database
1.04 - SELECT, interrogare il database
1.05 - Ricerca, filtraggio e ordinamento
1.06 - GROUP BY e funzioni di aggregazione
1.07 - UPDATE: modificare i dati
1.08 - DELETE, eliminare i record dalle tabelle
1.09 - Subquery
1.10 - Le transazioni
1.11 - Trigger
1.12 - Funzioni e procedure
1.13 - Modificare la struttura del database
1.14 - Utilizzo multiutente di un database
Dal sito HTML.it Guida linguaggio SQL
Chi ha necessità di utilizzare database sul proprio server non può prescindere dalla conoscenza di SQL (acronimo che
sta per Structured Query Language) il linguaggio per la creazione di database relazionali
Ritorna all'indice
1.01 - Database Relazionali
La struttura fondamentale del modello relazionale e’ appunto la relazione, cioe’ una tabella bidimensionale costituita
da righe (tuple) e colonne (attributi). Le relazioni rappresentano le entità che si ritiene essere interessanti nel database
Ogni istanza dell’entità trovera posto in una riga (tupla) della relazione, mentre gli attributi della relazione rappresente-
ranno le proprietà dell’entità.
Una relazione è la definizione della struttura della tabella, cioè il suo nome e l'elenco delle colonne (attributi) che la
compongono.
Ogni colonna (attributo) di una relazione è caratterizzato da un nome e da un dominio.
Il dominio indica quali valori possono essere assunti da una colonna della relazione (ad es. per il campo SESSO saranno
F, M). Una chiave esterna (foreign key) è una combinazione di attributi di una relazione che sono chiave primaria per
un’altra relazione.
Caratteristica fondamentale dei valori presenti in una chiave esterna è che, a meno che non siano null, devono corrispon-
dere a valori esistenti nella chiave primaria della relazione a cui si riferiscono.
Questa proprieta’ va sotto il nome di integrità referenziale (referential integrity)
Tutte le manipolazioni possibili sulle relazioni sono ottenibili grazie alla combinazione di cinque soli operatori:
REStrICT, PROJECT, TIMES, UNION e MINUS (operatori dell'algebra relazionale)
Per comodita’ sono stati anche definiti tre operatori addizionali che comunque possono essere ottenuti applicando i soli
cinque operatori fondamentali:
JOIN, INTERSECT e DIVIDE
Gli operatori relazionali ricevono come argomento una relazione o un insieme di relazioni
e restituiscono una singola relazione come risultato.
Vediamo brevemente questi otto operatori:
REStrICT: restituisce una relazione contenente un sottoinsieme delle tuple della relazione a cui
viene applicato. Gli attributi rimangono gli stessi.
PROJECT: restituisce una relazione con un sottoinsieme degli attributi della relazione a cui viene
applicato. Le tuple della relazione risultato vengono composte dalle tuple della relazione originale in modo che continuino
ad essere un insieme in senso matematico.
TIME: viene applicato a due relazioni ed effettua il prodotto cartesiano delle tuple.
Ogni tupla della prima relazione viene concatenata con ogni tupla della seconda.
JOIN: vengono concatenate le tuple di due relazioni in base al valore di un insieme dei loro attibuti.
UNION: applicando questo operatore a due relazioni compatibili, se ne ottiene una contenente le
tuple di entrambe le relazioni. Due relazioni sono compatibili se hanno lo stesso numero di attributi e gli attributi corrispon-
denti nelle due relazioni hanno lo stesso dominio.
MINUS: applicato a due relazioni compatibili, ne restituisce una terza contenente le tuple
che si trovano solo nella prima relazione.
INTERSECT: applicato a due relazioni compatibili, restituisce una relazione contenente le
tuple che esistono in entrambe le relazioni.
DIVIDE: applicato a due relazioni che abbiano degli attributi comuni, ne restituisce una
terza contenente tutte le tuple della prima relazione che possono essere fatte corrispondere a tutti i valori della seconda
relazione.
Nelle seguenti tabelle, a titolo di esempio, sono raffigurati i risultati dell’applicazione di alcuni operatori relazionali alle
relazioni Persone e Figli. Come nomi per le relazioni risultato si sono utilizzate le espressioni che le producono.
Persone
Figli
REStrICT (Persone)
sesso=’M’
PROJECT sesso (Persone)
JOIN (Persone, Figli)
Persone(numero_persona)=Figli(numero_persona)
I database relazionali compiono tutte le operazioni sulle tabelle utilizzando l’algebra relazionale, anche se normalmente
non permettono all’utente di utilizzarla.
L’utente interagisce con il database attraverso un’interfaccia differente, il linguaggio SQL, un linguaggio dichiarativo
che permette di descrivere insiemi di dati. Le istruzioni SQL vengono scomposte dal DBMS in una serie di operazioni
relazionali.
Ritorna all'indice
1.02 - Creare il database
Un database in un sistema relazionale e’ composto da un’insieme di tabelle, che corrispondono alle relazioni del modello
relazionale. Nella terminologia usata nell’SQL non si fa accenno alle relazioni, cosi’ come non viene usato il termine attributo,
ma viene usata la parola colonna, e non si parla di tupla, ma di riga.
Nel seguito verranno usate indifferentemente le due terminologie, quindi tabella varra’ per relazione, colonna per attributo, riga
per tupla, e viceversa.
In pratica la creazione del database consiste nella creazione delle tabelle che lo compongono.
In realta’ prima di poter procedere alla creazione delle tabelle normalmente occorre creare in effetti il database, il che di solito
significa definire uno spazio dei nomi separato per ogni insieme di tabelle.
In questo modo per un DBMS e’ possibile gestire piu’ database indipendenti contemporaneamente, senza che ci siano dei conflitti
con i nomi che vengono utilizzati in ciascuno di essi. Il sistema previsto dallo standard per creare degli spazi dei nomi separati
consiste nell’utilizzo dell’istruzione SQL CREATE SCHEMA. Di solito tale sistema non viene utilizzato (o almeno non con gli
scopi ed il significato previsti dallo standard), ma ogni DBMS prevede una procedura proprietaria per creare un database.
Normalmente viene esteso il linguaggio SQL introducendo un’istruzione non prevista nello standard: CREATE DATABASE.
La sintassi utilizzata da PostgreSQL, ma anche dai piu’ diffusi DBMS, e’ la seguente:
CREATE DATABASE nome_database
Una volta creato il database e’ possibile creare le tabelle che lo compogono. L’istruzione SQL preposta a questo scopo e’:
CREATE table nome_tabella (
nome_colonna tipo_colonna [ clausola_default ] [ vincoli_di_colonna ]
[ , nome_colonna tipo_colonna [ clausola_default ] [ vincoli_di_colonna ] … ]
[ , [ vincolo_di tabella] … ] )
Ad esempio se in Microsoft Access creo una nuova query e scrico il sequente codice:
CREATE table TAB1 (
COL1 CHAR,
COL2 INTEGER
)
avremo:
nome_colonna: e’ il nome della colonna che compone la tabella. Sarebbe meglio non esagerare con la lunghezza degli
identificatori di colonna, dal momento che l’SQL Entry Level prevede nomi non piu’ lunghi di 18 caratteri. Si consulti
comunque la documentazione dello specifico database. I nomi devono iniziare con un carattere alfabetico.
tipo_colonna: e’ l’indicazione del tipo di dato che la colonna potra’ contenere. I principali tipi previsti dallo standard
SQL sono:
CHARACTER(n): una stringa a lunghezza fissa di esattamente n caratteri. CHARACTER puo’ essere abbreviato
con CHAR
CHARACTER VARYING(n): una stringa a lunghezza variabile di al massimo n caratteri. CHARACTER VARYING
puo’ essere abbreviato con VARCHAR o CHAR VARYING.
INTEGER: un numero intero con segno. Puo’ essere abbreviato con INT. La precisione, cioe’ la grandezza del
numero intero che puo’ essere memorizzato in una colonna di questo tipo, dipende dall’implementazione del
particolare DBMS.
SMALLINT: un numero intero con segno con precisione non superiore a INTEGER.
FLOAT(p): un numero a virgola mobile, con precisione p. Il valore massimo di p dipende dall’implementazione
del DBMS. E’ possibile usare FLOAT senza indicazione della precisione, utilizzando quindi la precisione di default,
anch’essa dipendente dall’implementazione. REAL e DOUBLE PRECISION sono dei sinonimi per un FLOAT con
una particolare precisione. Anche in questo caso le precisioni dipendono dall’implementazione, con il vincolo che la
precisione del primo non sia superiore a quella del secondo.
DECIMAL(p,q): un numero a virgola fissa di almeno p cifre e segno, con q cifre dopo la virgola. DEC e’ un
abbreviazione per DECIMAL. DECIMAL(p) e’ un abbreviazione per DECIMAL(p,0).
Il valore massimo di p dipende dall’implementazione.
INTERVAL: un periodo di tempo (anni, mesi, giorni, ore, minuti, secondi e frazioni di secondo).
DATE, TIME e TIMESTAMP: un preciso istante temporale. DATE permette di indicare l’anno, il mese e il giorno.
Con TIME si possono specificare l’ora, i minuti e i secondi. TIMESTAMP e’ la combinazione dei due precedenti.
I secondi sono un numero con la virgola, permettendo cosi’ di specificare anche frazioni di secondo.
clausola_default: indica il valore di default che assumera’ la colonna se non gliene viene assegnato uno esplicitamente nel
momento della creazione della riga. La sintassi da utilizzare e’ la seguente:
DEFAULT { valore | NULL }
dove, valore e’ un valore valido per il tipo con cui la colonna e’ stata definita.
vincoli_di_colonna: sono vincoli di integrita’ che vengono applicati al singolo attributo. Sono:
NOT NULL, che indica che la colonna non puo’ assumere il valore NULL.
PRIMARY KEY, che indica che la colonna e’ la chiave primaria della tabella.
una definizione di riferimento, con cui si indica che la colonna e’ una chiave esterna verso la tabella e i campi
indicati nella definizione. La sintasi e’ la seguente:
REFERENCES nome_tabella [ ( colonna1 [ , colonna2 ... ] ) ]
[ ON DELETE { CASCADE | SET DEFAULT | SET NULL } ]
[ ON UPDATE { CASCADE | SET DEFAULT | SET NULL } ]
Le clausole ON DELETE e ON UPDATE indicano quale azione deve essere compiuta nel caso in cui una riga (tupla)
nella tabella referenziata venga eliminata o aggiornata. Infatti in tali casi nella colonna referenziante (che e’ quella
che si sta definendo) potrebbero esserci dei valori inconsistenti. Le azioni possono essere:
CASCADE: eliminare la tupla contenente la colonna referenziante (nel caso di ON DELETE) o aggiornare anche
la colonna referenziante (nel caso di ON UPDATE).
SET DEFAULT: assegnare alla colonna referenziante il suo valore di default.
SET NULL: assegnare alla colonna referenziante il valore NULL.
un controllo di valore, con il quale si permette o meno l’assegnazione di un valore alla colonna, in base al risultato di
un espressione. La sintassi da usare e’:
CHECK (espressione_condizionale)
dove espressione_condizionale e’ un’espressione che restituisce vero o falso.
Ad esempio, se stiamo definendo la colonna COLONNA1, definendo il seguente controllo:
CHECK ( COLONNA1 < 1000 )
in tale colonna potranno essere inseriti solo valori inferiori a 1000.
vincoli_di_colonna: sono vincoli di integrita’ che possono riferirsi a piu’ colonne della tabella. Sono:
la definizione della chiave primaria:
PRIMARY KEY ( colonna1 [ , colonna2 ... ] )
Si noti che in questo caso, a differenza della definizione della chiave primaria come vincolo di colonna, essa puo’ essere
formata da piu’ di un attributo.
le definizioni delle chiavi esterne:
FOREIGN KEY ( colonna1 [ , colonna2 ... ] ) definizione_di_riferimento
La definizione_di_riferimento ha la stessa sintassi e significato di quella che puo’ comparire come vincolo di colonna.
un controllo di valore, con la stessa sintassi e significato di quello che puo’
essere usato come vincolo di colonna.
Per chiarire meglio l’utilizzo dell’istruzione CREATE table, esaminiamo alcuni comandi che implementano il database
bibliografico di esempio.
CREATE table Publication (
ID INTEGER PRIMARY KEY,
type CHAR(18) NOT NULL
);
In Access avremo:
La precedente istruzione crea la tabella Publication, formata dalle due colonne ID di tipo INTEGER, e type di tipo CHAR(18).
ID e’ la chiave primaria della relazione. Sull’attributo type e’ posto un vincolo di non nullita’.
CREATE table Book (
ID INTEGER PRIMARY KEY REFERENCES Publication(ID),
title VARCHAR(160) NOT NULL,
publisher INTEGER NOT NULL REFERENCES Publication(ID),
volume VARCHAR(16),
series VARCHAR(160),
edition VARCHAR(16),
pub_month CHAR(3),
pub_year INTEGER NOT NULL,
nota VARCHAR(255)
);
In Access avremo la creazione della nuova tabelle e della relazione tra book e Pubblication:
Creiamo anche:
CREATE table Person (
ID INTEGER PRIMARY KEY,
Names CHAR(18) NOT NULL,
Surname CHAR(18) NOT NULL,
Address CHAR(32),
CAP INTEGER,
City CHAR(18),
Country CHAR(2)
);
Crea la relazione Book, formata da nove attributi. La chiave primaria e’ l’attributo ID, che e’ anche una chiave esterna
verso la relazione Publication. Sulle colonne (attributi) title, publisher e pub_year sono posti dei vincoli di non nullità.
Inoltre l’attributo publisher e’ una chiave esterna verso la tabella Publisher.
CREATE table Author (
publicationID INTEGER REFERENCES Publication(ID),
personID INTEGER REFERENCES Person(ID),
PRIMARY KEY (publicationID, personID)
);
Crea la relazione Author, composta da due attributi: publicationID e personID. La chiave primaria in questo caso è
formata dalla combinazione dei due attributi, come indicato dal vincolo di tabella PRIMARY KEY. PublicationID è
una chiave esterna verso la relazione Publication, mentre personID lo e’ verso la relazione Person.
Ritorna all'indice
1.03 - Popolare il database
INSERT, inserire una riga in una tabella
L’istruzione SQL che effettua l’inserimento di una nuova riga in una tabella è INSERT. La sintassi con cui essa viene usata
piu’ comunemente è:
INSERT INTO nome_tabella [ ( elenco_campi ) ] VALUES ( elenco_valori )
nome_tabella: è il nome della tabella in cui deve essere inserita la nuova riga;
elenco_campi: è l’elenco dei nomi dei campi a cui deve essere assegnato un valore, separati fra loro da una virgola.
I campi non compresi nell’elenco assumeranno il loro valore di default o NULL se non hanno un valore di default.
È un errore non inserire nell’elenco un campo che non abbia un valore di default e non possa assumere il valore NULL.
Se l’elenco non viene specificato dovranno essere specificati i valori di tutti i campi della tabella.
elenco_valori: è l’elenco dei valori che saranno assegnati ai campi della tabella, nell’ordine e numero specificati
dall’elenco_campi o in quello della definizione della tabella (se elenco_campi non viene specificato).
I valori possono essere un’espressione scalare del tipo appropriato per il campo o le keyword DEFAULT o NULL, se il campo
prevede un valore di default o ammette il valore NULL.
Il precedente esempio di inserimento viene quindi eseguito tramite le seguenti istruzioni SQL (una alla volta in ACCESS):
INSERT INTO Person VALUES ( 1, 'Agosti', 'Maristella' );
INSERT INTO Person VALUES ( 2, 'Benfante', 'Lucio' );
INSERT INTO Person VALUES ( 3, 'Melucci', 'Massimo' );
INSERT INTO Person VALUES ( 4, 'Spaccapietra', 'S.' );
INSERT INTO Person VALUES ( 5, 'Maryansky', 'F.' );
INSERT INTO Institution ( ID, name, city, country )
VALUES ( 1, '7th IFIP 2.6 Working Conference on Database Semantics (DS-7)',
'Leysin', 'Switzerland' );
INSERT INTO Publication VALUES ( 1, 'Proceedings' );
INSERT INTO Publication VALUES ( 2, 'InProceedings' );
INSERT INTO Proceedings ( ID, title, organization, pub_month, pub_year )
VALUES ( 1, 'Searching for Semantics: Data Mining, Reverse Engineering',
1, 'Oct', 1997 );
INSERT INTO InProceedings ( ID, proceedingsID, title, pages )
VALUES ( 2, 1,
'OFAHIR: "On-the-Fly" Automatic Authoring of Hypertexts for Information Retrieval',
'129-154' );
INSERT INTO Author VALUES ( 2, 1 );
INSERT INTO Author VALUES ( 2, 2 );
INSERT INTO Author VALUES ( 2, 3 );
INSERT INTO Editor VALUES ( 1, 4 );
INSERT INTO Editor VALUES ( 1, 5 );
Un’altra forma abbastanza utilizzata dell’istruzione INSERT segue la seguente sintassi:
INSERT INTO nome_tabella [ ( elenco_campi ) ]
istruzione_select
L’unica differenza con la precedente sintassi consiste nella sostituzione della clausola VALUES con un istruzione SELECT.
SELECT permette di estrarre dalle tabelle del database dei dati che vengono organizzati in una nuova relazione.
La precedente istruzione INSERT permette quindi di inserire nella tabella e nei campi specificati dati provenienti da altre tabelle.
Ovviamente, affinché l’istruzione venga eseguita con successo, i dati prodotti dall’istruzione SELECT dovranno essere compatibili
con i vincoli ed i domini dei campi della tabella in cui si sta effettuando l’inserimento.
Ritorna all'indice
1.04 - SELECT, interrogare il database
La SELECT è l'istruzioni che ci permettono di estrarre i dati che ci interessano.
La sintassi completa è la seguente:
SELECT [DISTINCT ] lista_elementi_selezione
FROM lista_riferimenti_tabella
[ WHERE espressione_condizionale ]
[ GROUP BY lista_colonne ]
[ HAVING espressione_condizionale ]
[ ORDER BY lista_colonne ]
Le uniche clausole obbligatorie sono SELECT e FROM.
Quest’ultima, detta clausola di selezione, stabilisce da quale tabella estrarre i dati, mentre la prima è detta
clausola di proiezione e stabilisce quali colonne devono essere riportate nel risultato finale.
La clausola WHERE definisce invece un filtro sulle righe che verranno analizzate, mentre ORDER BY indica l’ordinamento da
applicare al risultato finale. Se WHERE non viene specificata, non viene applicato alcun filtro sulle righe, che quindi vengono
esaminate tutte. Se invece non viene specificato alcun ordinamento, le righe vengono restituite senza un ordine definito,
generalmente così come vengono trovate in tabella.
Per quanto riguarda selezione e la proiezione, di cui ci occupiamo in questa lezione, vediamo subito un esempio:
SELECT Person.surname FROM Person
Questa query estrae dalla tabella Person tutti gli elementi, restituendo solamente la colonna surname.
Si possono indicare più colonne separandole con una virgola:
SELECT Person.surname, Person.names FROM Person
La sintassi completa prevede di specificare la colonna con la tabella di appartenenza;
quest’ultima, inoltre, si può omettere se non ci sono ambiguità:
SELECT surname, names FROM Person
Si può utilizzare l’asterisco (*) per indicare che si vogliono analizzare tutte le colonne di una tabella. Ad esempio:
SELECT Person.* FROM Person
che, se non ci sono ambiguità, equivale alla forma più comunemente utilizzata:
SELECT * FROM Person
È possibile anche specificare dei nomi alternativi (alias) per le colonne restituite come risultato, usando la parola chiave AS:
SELECT surname, names AS Allias_Names FROM Person
Il risultato produrrà due colonne, la prima denominata surname, e la seconda Allias_Names (in virtù dell’alias definito).
L’uso degli alias è particolarmente comodo quando si usano le espressioni: infatti, è possibile utilizzare espressioni SQL nella
clausola di proiezione per effettuare calcoli o elaborazioni sui valori presenti nei campi.
Ad esempio, la seguente interrogazione restituisce il cognome e l’iniziale del nome di ogni persona:
SELECT surname, MID(names, 1, 1) AS Iniziale FROM Person
PS: SUBSTRING in Access è sostituita da MID
Le possibili espressioni utilizzabili sono moltissime e dipendono in gran parte dal database utilizzato, quindi spesso le stesse
funzioni hanno nomi o sintassi diverse in MySQL, Oracle o SQL Server. Purtroppo ciò limita la portabilità delle espressioni tra i
vari database. Per i dettagli è quindi necessario ricorrere alla documentazione ufficiale dei singoli database.
Ci sono funzioni per lavorare con le date, con i numeri, con le stringhe di testo o per gestire i valori NULL.
Ad esempio, la funzione COALESCE restituisce il primo valore non nullo tra le espressioni indicate:
SELECT COALESCE(surname, given_names, 'Senza nome') AS name FROM Person
Esistono anche funzioni condizionali, come ad esempio CASE, che restituisce un valore in base al soddisfacimento di un certo predicato.
Per esempio, la seguente query calcola l’iniziale solo quando (CASE WHEN) il nome della persona è più lungo (LENGTH) di cinque caratteri:
SELECT CASE WHEN LENGTH(given_names) > 5
THEN CONCAT(SUBSTRING(given_names, 1, 1), '.')
ELSE given_names
END AS Abbrev
FROM Person
Si possono concatenare più espressioni CASE WHEN, esattamente come un’espressione else if del C o degli altri linguaggi imperativi.
Nel prossimo capitolo vedremo una particolare classe di funzioni, dette di aggregazione, che possono essere usate per eseguire operazioni
quali conteggi, somme, medie.
DISTINCT, eliminare i duplicati
La prima query che abbiamo visto restituirebbe righe duplicate nel caso in cui nella tabella fossero presenti persone con lo stesso cognome.
Per evitare ciò occorre specificare l’opzione DISTINCT:
SELECT DISTINCT surname FROM Person
Questa opzione fa sì che nel risultato non ci siano righe uguali tra loro, ed è utile soprattutto quando si lavora con i join.
Un join (congiunzione) è una funzione che stabilisce come combinare righe di due tabelle diverse per ottenere una tabella unica.
Si distinguono generalmente tre tipi di join, che vediamo di seguito.
Inner join
Questa operazione, che in SQL è implementata dalla clausola INNER JOIN o semplicemente JOIN, combina solo le righe delle due tabelle che
soddisfano un certo predicato di confronto, come in un’operazione di intersezione.
Si possono effettuare confronti tra date o numeri; ad esempio per avere tutti i libri usciti dopo la data di pubblicazione di ogni articolo:
SELECT Book.title, Article.title
FROM Book
JOIN Article on Article.pub_year > Book.pub_year
Comunque nella maggior parte dei casi questo predicato è un’uguaglianza tra ID:
SELECT surname, given_names, title
FROM Person
JOIN Author on person.ID = Author.personID
JOIN Book on Book.ID = Author.publicationID
Questa query restituisce i nominativi e i titoli dei libri pubblicati.
Outer join
La query precedente restituirà solo i libri e le persone per cui esiste un collegamento.
Quindi non restituirà né le persone che non hanno scritto libri né i libri senza autore.
A seconda se vogliamo anche questi avremo rispettivamente un LEFT OUTER o un RIGHT OUTER JOIN. Ad esempio:
SELECT surname, given_names, title
FROM Person
LEFT JOIN Author on person.ID = Author.personID
LEFT JOIN Book on Book.ID = Author.publicationID
Come si vede la parola chiave OUTER è opzionale.
In questo caso, per le persone che non hanno pubblicato un libro, la colonna title avrà valore NULL.
MySQL non supporta nativamente il FULL OUTER JOIN, che permette di fare un join destro e sinistro simultaneamente, per ottenere,
nel nostro caso, oltre agli autori e ai libri anche i libri pubblicati anonimamente e le persone che non hanno scritto libri:
SELECT surname, given_names, title
FROM Person
FULL JOIN Author on person.ID = Author.personID
FULL JOIN Book on Book.ID = Author.publicationID
Unioni
L’operazione sistemistica di unione si può fare in SQL utilizzando la parola chiave UNION.
Ad esempio, per effettuare qualcosa di simile ad un FULL JOINcon MySQL si può scrivere:
SELECT surname, given_names, title FROM Person
LEFT JOIN Author on person.ID = Author.personID
LEFT JOIN Book on Book.ID = Author.publicationID
UNION
SELECT surname, given_names, title FROM Person
RIGHT JOIN Author on person.ID = Author.personID
RIGHT JOIN Book on Book.ID = Author.publicationID
Nell’uso di UNION bisogna fare in modo che le colonne delle varie SELECT da concatenare abbiano lo stesso numero e siano dello
stesso tipo altrimenti si avrà un errore.
Proprio come l’operazione sistemistica di unione, questa ignora le righe duplicate. Quindi, come per DISTINCT, è necessario prestare
attenzione nell’utilizzo perché se le righe restituite sono in grande quantità, l’eliminazione dei duplicati può impegnare molto tempo
il server. Per evitare la ricerca dei duplicati su deve usare l’istruzione UNION ALL:
SELECT surname, given_names, title FROM Person
JOIN Author on person.ID = Author.personID
JOIN Book on Book.ID = Author.publicationID
UNION ALL
SELECT surname, given_names, title FROM Person
JOIN Author on person.ID = Author.personID
JOIN Article on Article.ID = Author.publicationID
Ritorna all'indice
1.05 - Ricerca, filtraggio e ordinamento
Continuiamo con l’istruzione SELECT e stavolta vediamo le clausole WHERE e ORDER BY.
Filtraggio
Già nella lezione precedente, quando abbiamo parlato di inner join, abbiamo visto un particolare tipo di filtraggio, l’intersezione tra insiemi.
Ora vediamo invece come, con la clausola WHERE, possiamo filtrare le nostre righe in modo molto preciso sulla base dei valori che assumono i
campi.
In generale, la sintassi di questa clausola è un espressione che può essere o vera o falsa per una certa riga del nostro insieme di dati.
L’espressione più semplice ovviamente è l’eguaglianza:
SELECT *
FROM Person
WHERE surname = 'Knuth'
In questo caso verranno restituite le persone aventi un certo cognome. Usando l’operatore AND possiamo combinare due espressioni per richiedere
di restituire le righe che soddisfano entrambe le condizioni:
SELECT * FROM Person
WHERE surname = 'Knuth' AND given_names = 'Donald'
In questo caso, nel nostro database di esempio, non avremo alcun risultato perché l’operatore di uguaglianza (=) viene soddisfatto solo se i
valori sono esattamente uguali, mentre ‘Donald E.‘ è diverso da ‘Donald‘. Con l’operatore LIKE, utilizzabile solamente con i dati di tipo testuale
come VARCHAR, possiamo invece cercare i valori che corrispondono ad un certo pattern. Si possono utilizzare due caratteri jolly:
- il carattere % verrà soddisfatto da qualsiasi sequenza di caratteri trovata;
- il carattere _ verrà soddisfatto da qualsiasi carattere (singolo) trovato.
Stavolta, quindi, questa interrogazione restituirà esattamente il record di Donald E. Knuth:
SELECT * FROM Person
WHERE surname = 'Knuth' AND given_names LIKE 'Donald%'
Se non viene specificato alcun carattere jolly, LIKE si comporta esattamente come un’uguaglianza semplice.
Si noti che MySQL supporta anche la parola chiave REGEXP, che effettua un confronto tramite espressioni regolar, molto più potenti della
ricerca tramite LIKE. Oracle supporta la funzione REGEXP_LIKE. Ad esempio, con la seguente interrogazione MySQL si ottengono i libri che
nel titolo contengono prima il testo “Object” poi qualsiasi carattere, quindi il testo “Oriented” e infine un testo che può essere o
"Databases" o "Concepts":
SELECT * FROM Book
WHERE title REGEXP 'Object.*Oriented.*(Databases|Concepts)'
In Oracle la sintassi è leggermente diversa:
REGEXP_LIKE(title, 'Object.*Oriented.*(Databases|Concepts)')
Per dettagli sulle espressioni regolari supportate dai database, rimandiamo alle guide ufficiali dei rispettivi database.
Oltre all’operatore AND, le espressioni si possono combinare con l’operatore OR, soddisfatto quando almeno una delle espressioni è vera:
SELECT * FROM Book WHERE pub_year = 1983 OR pub_year = 1993 OR pub_year = 1980
Questo tipo di filtri si può scrivere in maniera più compatta e più comoda con l’operatore IN:
SELECT * FROM Book WHERE pub_year IN (1983, 1993, 1980)
L’operato IN permette di indicare un elenco di possibili valori. Non solo: è possibile specificare anche query secondarie:
SELECT * FROM Book WHERE pub_year IN (SELECT pub_year FROM Article)
Nel combinare espressioni AND e OR, dobbiamo ricordare che AND ha la precedenza nella valutazione. Quindi (come per la maggior parte dei
linguaggi) dobbiamo usare le parentesi per modificare l’ordine degli operatori, altrimenti, ad esempio, questa interrogazione potrebbe dare
risultati inaspettati:
SELECT * FROM Book
WHERE (pub_year = 1983 OR pub_year = 1993) AND publisher = 1
Infatti, eseguendola senza le parentesi, otterremmo anche i libri pubblicati nel 1983, indipendentemente dall’editore, oltre ai libri
pubblicati nel 1993 dall’editore avente ID uguale a 1.
Un altro operatore logico (stavolta unario) importante è il NOT, utilizzato per negare una condizione:
SELECT * FROM Book WHERE NOT(volume = 1)
Contrariamente a quanto si possa pensare, questa query non restituirà né i volumi diversi dal primo, ma neanche i libri aventi il
campo volume impostato a NULL. Questo perché in SQL il valore NULL è speciale nel senso che le espressioni che contengono valori nulli
vengono valutate come NULL, quindi richiedono i seguenti operatori speciali:
- IS NULL
- IS NOT NULL
Possiamo quindi riscrivere la query dell’esempio così:
SELECT * FROM Book WHERE volume IS NULL OR NOT(volume = 1)
Oppure possiamo utilizzare la funzione COALESCE che abbiamo visto nella lezione precedente:
SELECT * FROM Book WHERE NOT(COALESCE(volume, 0) = 1)
Naturalmente, si può testare anche la disuguaglianza, con gli appositi operatori:
disuguaglianza semplice: < >
minore < e minore o uguale: < =
maggiore > e maggiore o uguale: > =
Nel caso dei valori testuali, come CHAR e VARCHAR, l’ordinamento è quello alfabetico in base alla codifica scelta per il database.
L’operatore BETWEEN è comodo per cercare valori in un intervallo, ad esempio di date, ma può essere usato anche con gli altri tipi.
L’intervallo si intende chiuso (con gli estremi); quindi con la seguente interrogazione avremo anche i libri pubblicati nel 1980 e nel 1993:
SELECT title, pub_year
FROM Book
WHERE pub_year BETWEEN 1980 AND 1993
Ordinamento
Per ordinare i risultati di una query si usa la clausola ORDER BY, seguita dalle espressioni da valutare per effettuare l’ordinamento.
Ad esempio:
SELECT title, series, pub_year FROM Book
ORDER BY title
Questa query restituisce i libri pubblicati in ordine di titolo; se li volessimo ordinati prima per anno di pubblicazione e poi per titolo,
basterebbe modificare la clausola in questo modo:
SELECT title, series, pub_year FROM Book
ORDER BY pub_year DESC, title
Abbiamo semplicemente separato i due campi da una virgola. Con la parola chiave DESC abbiamo specificato che vogliamo un ordinamento
discendente, ossia mettendo prima i più recenti. La parola chiave ASC, che indica l’ordinamento ascendente, è opzionale perché corrisponde
ll’ordinamento di default.
Il comportamento di ORDER BY con i valori nulli dipende dal tipo di database utilizzato. Ad esempio MySQL e SQL Server li mettono sempre
in testa, mentre Oracle li mette in coda. Molti database, come Oracle e PostgreSQL hanno le parola chiave NULLS FIRST e NULLS LAST per
indicare come trattare i valori nulli; la seguente query, in Oracle, restituisce prima i libri ordinati per collana, mettendo in testa i libri
che non appartengono ad una collana:
SELECT title, series FROM Book ORDER BY series NULLS FIRST
Considerato che nella clausola di ordinamento possiamo usare tutte le funzioni che abbiamo già visto (non quelle di aggregazione che vedremo
nella prossima lezione), un modo portabile per ottenere lo stesso effetto è di utilizzare la funzione COALESCE. Infatti, anche con MySQL
possiamo ottenere lo stesso effetto scrivendo:
SELECT title, series FROM Book ORDER BY COALESCE(series, '0')
In questo modo i valori nulli verranno considerato come una stringa contenente il solo zero.
Ritorna all'indice
1.06 - GROUP BY e funzioni di aggregazione
Con la parola chiave DISTINCT, nella lezione 10, abbiamo visto la forma più semplice di aggregazione: quella che distingue le righe uguali
riportandole quindi una sola volta. L’esempio ivi riportato è, infatti, riscrivibile utilizzando la clausola GROUP BY; in altre parole,
le seguenti due istruzioni sono equivalenti:
SELECT DISTINCT surname FROM Person
SELECT surname FROM Person GROUP BY surname
Se le eseguiamo nel nostro database di esempio, otteniamo dunque gli stessi risultati. La clausola GROUP BY serve a specificare quali sono i
campi sui cui effettuare i raggruppamenti: il motore di query, per ogni riga esaminerà tali campi e la classificherà nel gruppo corrispondente.
Si possono specificare calcoli da effettuare per ogni gruppo. Ad esempio la query seguente restituisce, per ogni gruppo (surname), il numero di
occorrenze trovate, cioè quante persone hanno quel cognome:
SELECT surname, COUNT(*)
FROM Person
GROUP BY surname
In questo caso il motore raggruppa le righe in base al cognome, e per ogni gruppo effettua il conteggio di quanti elementi sono presenti nel
gruppo.
Da quanto detto, possiamo desumere una naturale limitazione di SQL: se viene specificata una clausola GROUP BY, allora nella clausola SELECT
deve esserci:
- o un campo specificato nella clausola GROUP BY;
- oppure una funzione di aggregazione.
Questo perché quando il motore aggrega le righe deve sapere come comportarsi per ogni campo da restituire.
Funzioni di aggregazione
Le funzioni di aggregazione sono particolari funzioni che operano su più righe. In generale, non tutti i database supportano le stesse
funzioni, per cui è bene riferirsi alle guide ufficiali. Ad esempio MySql fornisce la funzione GROUP_CONCAT, che non è invece supportata su
Oracle.
Le funzioni più comuni sono:
- COUNT per effettuare conteggi nel gruppo;
- SUM per le somme;
- MAX e MIN;
- AVG per calcolare la media.
La funzione COUNT
Questa funzione può essere invocata in tre modi. Il più semplice COUNT(*) l’abbiamo già visto: effettua il conteggio di tutte le righe presenti
nel gruppo, indipendentemente dai valori assunti. Può essere usato anche senza GROUP BY, per calcolare le righe totali presenti in una tabella:
SELECT COUNT(*) FROM Person
Se tra le parentesi specifichiamo un’espressione, verranno contate solo le righe che hanno quell’espressione non nulla. Ad esempio la seguente
query raggruppa i libri per anno di pubblicazione, contando quanti sono e quanti sono stati suddivisi per volume.
SELECT pub_year, COUNT(*), COUNT(volume)
FROM Book
GROUP BY pub_year
Se prima dell’espressione indichiamo la parola chiave DISTINCT verranno conteggiate solo le espressioni non nulle e distinte. In questo caso,
in cui abbiamo due tabelle in join, avremo come risultato il numero di redattori per ogni libro. Avremo vedremo uno 0 nel caso in cui il libro
non ha specificato nessun redattore nel database.
SELECT Book.title, COUNT(DISTINCT personID)
FROM Book
LEFT JOIN Editor ON book.ID = Editor.publicationID
GROUP BY Book.id, Book.title
La funzione SUM
Questa funzione somma i valori dei campi trovati nel gruppo. I valori nulli vengono ignorati, contrariamente a quanto farebbe una somma
semplice.
SELECT Person.surname, SUM(Book.pages)
FROM Book
JOIN Editor ON book.ID = Editor.publicationID
JOIN Person ON Editor.personID = Person.ID
GROUP BY Person.ID, Person.surname
Questa interrogazione restituisce il numero di pagine totale editate da ogni redattore.
Le funzioni MIN, MAX e AVG
Con queste funzioni si possono ottenere i valori massimo e minimo di una colonna, in base al criterio di ordinamento predefinito
(ad esempio per il testo verrà usato un ordinamento alfabetico). I valori nulli vengono ignorati.
Naturalmente più funzioni possono essere usate nella stessa query. La seguente restituisce, per ogni anno di pubblicazione, il primo titolo
in ordine alfabetico, il numero di libri pubblicati, la media del numero di pagine e il numero di pagine del libro più grande:
SELECT Book.pub_year, MIN(title), COUNT(*), AVG(pages), MAX(pages)
FROM Book
GROUP BY pub_year
Filtraggio sul raggruppamento
A differenza di WHERE, che agisce a livello di singola riga, la parola chiave HAVING permette di effettuare un filtraggio sul ragguppamento.
Questa clausola si inserisce subito dopo la GROUP BY.
Il criterio di filtraggio può contenere qualsiasi funzione di raggruppamento. Supponiamo di volere vedere in quali abbiamo pubblicato almeno
100 libri:
SELECT Book.pub_year, COUNT(*), AVG(pages)
FROM Book
GROUP BY pub_year HAVING COUNT(*) > 100
Analisi dei dati con ROLLUP e CUBE
Le direttive ROLLUP e CUBE, restituiscono nel risultato ulteriori righe per rappresentare aggregazioni trasversali, ad esempio per calcolare
subtotali. Vediamo un esempio concreto:
SELECT Book.pub_year, publisher, COUNT(*), SUM(pages)
FROM Book
GROUP BY pub_year, publisher WITH ROLLUP
Se eseguissimo questa query senza WITH ROLLUP otterremmo, per ogni anno e per ogni casa editrice, il numero di libri pubblicati e il totale
delle pagine. Ma se volessimo vedere anche l’aggregazione per anno indipendentemente dall’editore dovremmo effettuare un’altra query.
Utilizzando invece il rollup, possiamo vedere anche tali raggruppamenti trasversali, come vediamo dalle ultime tre righe restituite dalla query:
Come si vede, sono stati fatti anche i raggruppamenti per anno, indipendentemente dall’editore e i totali generali (ultima riga).
Non sono stati fatti invece i raggruppamenti per editore indipendentemente dall’anno. Per avere tutte le possibili combinazioni di raggruppamenti
si utilizza la direttiva WITH CUBE, per ottenere anche righe fatte così:
Ritorna all'indice
1.07 - UPDATE: modificare i dati
Dopo aver visto le modalità per inserire dati nel database e per interrogarlo, passiamo in rassegna le istruzioni per modificarne i dati.
Il comando UPDATE
Il comando UPDATE serve a modificare dati già inseriti nelle tabelle del database. La sua sintassi è la seguente:
UPDATE nome_tabella
SET nomeCampo1 = Grazie alle viste (e all’assegnazione oculata dei permessi agli utenti) e’ possibile fare in modo che utenti diversi abbiano una percezione della struttura del database anche molto diversa da quella che ha realmente e impedire che particolari categorie di utenti possano accedere ad informazioni che non competono loro. Ad esempio, supponiamo di avere una tabella in cui sono memorizzati i dati anagrafici dei dipendenti di una ditta e l’ammontare del loro stipendio mensile. Ovviamente si vorrebbe impedire la consultazione dei dati relativi agli stipendi a tutti gli utenti, eccetto a quelli che si devono occupare dell’erogazione/amministrazione degli stessi. Un sistema per fare cio’ e’ quello di definire una vista contenente solo le colonne dei dati anagrafici. In questo modo tutti gli utenti autorizzati ad accedere ai dati anagrafici, ma non a quelli degli stipendi, lo potranno fare solo attraverso tale vista. Ulteriori partizionamenti potrebbero essere fatti in senso orizzontale, creando ad esempio un vista che contiene solo le informazioni sui dirigenti ed una che contiene i dati degli altri dipendenti. Inoltre, le viste spesso contribuiscono a facilitare quella indipendenza fra applicazioni e struttura dei dati, che rende i database degli strumenti tanto utili. Infatti se ad un certo punto fosse necessario cambiare la struttura del database (scomponendo, ad esempio, una tabella in due tabelle per motivi di efficienza), non sarebbe necessario modificare tutte le applicazioni adattandole alla nuova struttura, ma sarebbe sufficiente creare delle opportune view, in modo che dal punto di vista delle applicazioni niente sia stato cambiato. |
---|