Che cos'è un database
Un Database è un insieme organizzato di dati: deve essere quindi visto come costituito da due entità
distinte, i dati e le regole di accesso.
Da un punto di vista puramente teorico i singoli record che compongono i dati potrebbero avere una struttura
libera, ma nella pratica (bisogna poi riuscire a costruire il Motore del Database, ovvero un'applicazione in
grado di implementare le Regole e fornire una interfaccia tra il Database e l'Utente) la struttura dei record è o
uguale per tutti o di alcuni tipi fissati. In quest'ultimo caso avremo un certo numero di sotto-insiemi di records
con struttura omogenea, che potremmo anche considerare come un certo numero di files distinti.
Le regole di accesso possono essere di qualsiasi tipo: il loro scopo è quello di permettere l'inserimento di
informazioni ed il loro successivo reperimento.
Un semplice file sequenziale non può quindi essere mai visto come un Database, poiché l'unica regola
possibile per ritrovare un record è leggerli tutti dal primo fino a quello cercato (magari l'ultimo).
Un passo avanti è il file ad accesso diretto (Random): in questo caso possiamo accedere direttamente al
record x° e quindi basterà avere un metodo per sapere che posizione occupa il record cercato per averlo
immediatamente disponibile.
Non converrà ovviamente complicarsi la vita con i Database se abbiamo a che fare con numeri di records
così piccoli da rendere ininfluente per l'Applicazione il tempo necessario per leggerli tutti.
Attenzione perché ininfluente è un concetto relativo: 10 secondi sono ininfluenti se per ogni dato devo
eseguire una elaborazione complessissima della durata di qualche minuto, ma 100ms sono una
esagerazione se il dato mi serve per una elaborazione in tempo reale.
Le prime applicazioni su Personal Computer (ambiente DOS)
Nel frattempo erano nati i primi Personal Computer; dopo pochi anni di vita cominciarono ad avere l'Hard
Disk e quindi la possibilità di archiviare i dati e sviluppare applicazioni gestionali.
Quasi a metà degli anni 80 nacque il primo prodotto di Database per PC ad opera della Ashon-Tate, il
dBase: agli inizi girava solo in interprete (esisteva anche un prodotto di run-time, ma ebbe scarsa rilevanza),
ma a partire dalla versione dBaseIII la Nantucket mise sul mercato un compilatore per i programmi dBIII in
grado di generare degli eseguibili indipendenti: il Clipper.
Nello stesso periodo nacque anche il Database della Fox.
I linguaggi di programmazione e le caratteristiche dei due prodotti erano abbastanza simili, tanto che il
linguaggio supportato dal compilatore Clipper (che era più esteso) permetteva di gestire i Db di entrambe le
case.
Il successo del prodotto fu notevole (versioni 1986 e 1987), al punto che praticamente tutte le applicazioni di
gestione dati erano in compilato Clipper con database in dBIII (o il successivo dBIV).
Inoltre essendo il Clipper scritto in C era anche possibile implementare nuove funzioni scrivendole in C e
creando con esse una nuova Libreria da passare al programma di Link all'atto della creazione del file .EXE.
Di particolare importanza furono le librerie prodotte da alcune case per la gestione della Grafica in quanto
Clipper usava esclusivamente lo schermo in modalità Testo (25 righe di 80 colonne), mentre con la nascita
della prima VGA IBM era possibile lavorare a 640x480 in 16 colori, rendendo così possibile realizzare
rappresentazioni grafiche (in genere diagrammi) di qualità soddisfacente.
I Database relazionali
Un Db si dice relazionale quando al suo interno è possibile stabilire relazioni tra sottoinsiemi di dati.
Facciamo un esempio: abbiamo 2 files, nel N.1 ci sono i dati di tutti gli impiegati di una Società, nel N.2 ci
sono i dati di tutti i reparti della Società; se nel record tipo 1 è presente il reparto di appartenenza allora
possiamo mettere in relazione gli impiegati con i rispettivi reparti.
Naturalmente l'esistenza di relazioni pone delle restrizioni: modifiche all'identificativo dei record tipo 2 o loro
cancellazione provoca la distruzione della relazione.
Un primo passo verso il Db relazionale nell'ambito Personal Computer fu l'introduzione sia per il dBaseIII che
per il Silver-Fox del comando SET RELATION TO .
Vediamone l'uso e le limitazioni.
Si usavano delle aree di lavoro, in ognuna delle quali era possibile aprire un solo Db per volta; la sequenza
dei comandi sarebbe stata:
SELECT 1 sceglie l'area 1
USE REPARTI INDEX REPARTI apre REPARTI.DBF con indice REPARTI.NDX (IDX per SilverFox)
SELECT 2
USE IMPIEGAT INDEX IMPIEGAT
SET RELATION TO REPARTO ON REPARTI
col vincolo che il campo REPARTO esistesse in entrambi e con lo stesso formato ed inoltre fosse la Chiave
dell'indice in uso per REPARTI
Allora per ogni record di IMPIEGATI erano disponibili, oltre ai campi di quel Db, anche i campi del Db
REPARTI:
ad esempio A–>NOMEREP, dove A–> indicava l'Alias del Db: A=area 1, B=area 2, etc..
Non era molto ma si incominciava ad introdurre il concetto.
Anche perché nel frattempo erano nati i primi veri Db relazionali, ma la complessità dei loro motori li
confinava su macchine più grandi e potenti: poveri PC con i loro 640Kb di RAM!.
Uno di quei primi Db relazionali è ancora oggi vivo e vegeto ed è Oracle, ma il requisito minimo per utilizzarlo
è un server Windows NT ed alcune decine di milioni per la licenza.
Quindi d'ora in avanti ci limiteremo a Database relazionali che siano compatibili con i nostri PC, in particolare
Microsoft Access (ed il relativo motore: MS Jet) che, pur non essendo l'unico, ha praticamente
monopolizzato il mercato in ambiente Windows. Pur essendo nato sotto Windows 3.1 la sua diffusione su
larga scala è iniziata con Windows 95 e la sua integrazione nel pacchetto MS Office.
SQL - Structured Query Language
La sua nascita è dovuta all'esigenza di stabilire una interfaccia standard di accesso ai Database
indipendentemente dal tipo e dalla piattaforma.
Esamineremo in dettaglio l'SQL supportato dal motore MS Jet (quello dei Data Access Object di Visual
Basic) e dei comandi SQL ignoreremo in genere quelli non utilizzabili in Visual Basic.
» SELECT Statement
Ordina al motore Microsoft Jet di recuperare informazioni da un database sotto forma di un insieme di
records.
Sintassi:
SELECT [predicate] { * | table.* | [table.]field1 [AS alias1] [, [table.]field2 [AS alias2] [, ...]]}
FROM tableexpression [, ...] [IN externaldatabase]
[WHERE... ]
[GROUP BY... ] [HAVING... ]
[ORDER BY... ]
[WITH OWNERACCESS OPTION]
Parti del comando SELECT:
predicate: uno dei seguenti: ALL, DISTINCT, DISTINCTROW, o TOP (vedremo più avanti i dettagli).
Si usa il predicate per restringere il numero dei records restituiti; se omesso il default è ALL.
(*): Indica che sono scelti tutti i campi della Table (o delle Tables).
table Il nome della Table contenente i campi dalla quale vengono scelti i records.
field1, field2 Il nome dei campi contenenti i dati cercati. Se si includono più campi vengono restituiti
nell'ordine elencato.
alias1, alias2 I nomi da usare come testata delle colonne invece dei nomi di colonna originali della Table.
Da notare che qualora i campi provengano da più Tables in caso di sinonimi occorre utilizzare la sintassi
nometable.nomefield per determinare un campo in modo univoco.
» clausola FROM
tableexpression: una espressione che identifica una o più Tables contenenti i dati da cercare; l'espressione
può essere il nome di una Table, il nome di una Query salvata o il risultato di una INNER JOIN, LEFT JOIN,
o RIGHT JOIN (vedremo più avanti i dettagli).
externaldatabase: il nome del Database contenente le Tables in tableexpression se non è il Database
corrente.
» clausola WHERE
Indica il criterio di scelta: l'elemento base è nomefield operatore di confronto valore|nomefield; possono
essere concatenati più elementi base tramite gli operatori logici (And Or).
Esempio: WHERE NomeImpiegato="Paolo" And (ImportoOrdini>10000000 Or NumeroOrdini>100)
Note:
nell'eseguire questa operazione il motore database Microsoft Jet cerca la Table (o Tables) specificata,
estrae le colonne (=campi) scelte, individua le righe (=record) che soddisfano il criterio ed ordina o
raggruppa le righe risultanti nell'ordine specificato.
Il comando SELECT non modifica i dati del database.
La sintassi minima è:
SELECT fields FROM table
Usando un aterisco (*) si scelgono tutti i campi di una Table; l'esempio seguente: SELECT * FROM
Employees è equivalente ad usare come RecordSet Employees.
» Clausola GROUP BY
Combina i records con valori identici del campo specificato in un singolo record. Un valore riepilogativo è
creato se nel comando SELECT viene inclusa una funzione SQL di aggregazione, quale Sum o Count.
Sintassi:
[GROUP BY groupfieldlist]
groupfieldlist Il nome di fino a 10 campi usati per raggruppare i records; l'ordine dei campi in groupfieldlist
determina il livello di grouping da quello più alto a quello più basso.
Note:
I valori riepilogativi vengono omessi se non è indicata una funzione SQL di aggregazione nel comando
SELECT.
Valori Null nei campi di GROUP BY vengono raggruppati e non saltati; tuttavia i valori Null non vengono
considerati da alcuna funzione SQL di aggregazione.
Usare la clausola WHERE per escludere i records che non si vuole raggruppare, usare la calusola HAVING
per filtrare i records dopo che sono stati raggruppati.
Non possono essere usati come campi GROUP BY né campi Memo né campi OLE Object; possono invece
essere usati tutti gli altri campi di qualsiasi Table indicata nella clausola FROM, anche se detti campi non
sono inclusi nella lista della SELECT purché sia inclusa almeno una funzione SQL di aggregazione.
» Clausola HAVING
Specifica quali records raggruppati vengono restituiti da un comando SELECT con clausola GROUP BY.
Sintassi:
SELECT .........
FROM ...........
WHERE ..........
GROUP BY groupfieldlist
HAVING groupcriteria
groupfieldlist: già esaminato in precedenza.
groupcriteria: un'espressione che determina quali records raggruppati restituire; possono essere usate fino
a 40 espressioni collegate dagli operatori And e Or.
Esempio:
SELECT CategoryID, Sum(UnitsInStock)
FROM Products
GROUP BY CategoryID
HAVING Sum(UnitsInStock) > 100
Dalla Table Products per ogni CategoryID presente otteniamo un record con 2 campi (CategoryID e
UnitsInStock) contenente nel 2° la somma dei valori di tutti i records avente uguale CategoryID solo se detta
somma è maggiore di 100.
» Clausola ORDER BY
Ordina i records risultato di una query secondo uno o più campi specificati in sequenza ascendente o
discendente.
Sintassi:
SELECT .........
FROM ...........
[WHERE ..........]
[GROUP BY ...... [HAVING ....]]
[ORDER BY field1 [ASC|DESC][, field2 [ASC|DESC]][, ...]]]
field1, field2, I nomi dei campi di sort.
Note:
l'ordine di default è ascendente; i due esempi seguenti sono equivalenti:
SELECT LastName, FirstName
FROM Employees
ORDER BY LastName
SELECT LastName, FirstName
FROM Employees
ORDER BY LastName ASC
Per ordinare in senso discendente (Z ad A, 9 a 0) aggiungere DESC dopo il nome del campo; l'esempio
seguente ordina per stipendi decrescenti:
SELECT LastName, FirstName, Salary
FROM Employees
ORDER BY Salary DESC, LastName
Non è possibile specificare nella clausola ORDER BY né campi Memo né campi OLE Object; i records
vengono ordinati sul 1° campo e quelli con ugual valore vengono quindi ordinati sul 2° e così via.
Dichiarazione WITH OWNERACCESS OPTION
In un ambiente multiutente con sicurezze a livello di workgroup, utilizzare questa dichiarazione con una
query per dare all'utente della query le stesse abilitazioni del proprietario della query.
L'esempio seguente abilita l'utente a vedere dati salariali (anche se non è abilitato ad accedere alla Table
Stipendi) purché il proprietario della query abbia tale abilitazione:
SELECT Cognome, Nome, Salario
FROM Stipendi
ORDER BY Cognome
WITH OWNERACCESS OPTION
Questa opzione richiede che abbiate accesso al file System.mdw associato al Database; è utile solo in
implementazioni multiutente protette.
» Predicati ALL, DISTINCT, DISTINCTROW, TOP
Specificano i records restituiti dalla query SQL.
Sintassi.
SELECT [ALL | DISTINCT | DISTINCTROW | [TOP n [PERCENT]]]
FROM table [ altre clausole .......]
ALL = tutti; assunto di default: il motore Microsoft Jet sceglie tutti i records che soddisfano le condizioni nel
comando SQL.
DISTINCT Omette i records contenenti dati duplicati nei campi indicati; per esempio più impiegati nella Table
Employees possono avere lo stesso LastName.
Se 2 records contengono Smith nel campo LastName il seguente comando SQL ritorna un solo record
contenente Smith:
SELECT DISTINCT LastName FROM Employees
L'output di una query che usa DISTINCT non è Updatable.
DISTINCTROW: omette i dati basati su records interamente duplicati. Per esempio voi create una query che
unisce le Tables Customers e Orders sulla base del campo CustomerID; Customers non contiene
CustomerID duplicati, ma Orders può in quanto ogni customer può avere vari orders. Il seguente comando
SQL produce una lista delle compagnie che hanno almeno un ordine (anche se senza alcun dettaglio
riguardo agli ordini):
SELECT DISTINCTROW CompanyName
FROM Customers INNER JOIN Orders
ON Customers.CustomerID = Orders.CustomerID
ORDER BY CompanyName
DISTINCTROW ha effetto solo si scelgono campi da alcune ma non tutte le Tables usate nella query ed è
ignorata se la query include una sola Table o se si scelgono campi da tutte le Tables.
TOP n [PERCENT]: ritorna solo un cero numero dei records che cadono all'inizio o alla fine di una
graduatoria specificata da una clausola ORDER BY ; vogliamo i nomi dei migliori 25 studenti diplomati nel
1994:
SELECT TOP 25 FirstName, LastName
FROM Students
WHERE GraduationYear = 1994
ORDER BY GradePointAverage DESC
Se si omette la clausola ORDER BY la query ritorna un insieme arbitrario di 25 records della Table Students
che soddisfano la clausola WHERE.
TOP non sceglie tra valori uguali: se il 25° ed il 26° studente hanno la stessa media la query restituisce 26
records.
Se si usa PERCENT viene restituita quella percentuale dei records che soddisfano la query: se invece dei
migliori 25 vogliamo il 10% peggiore dei diplomati 1994:
SELECT TOP 10 PERCENT FirstName, LastName
FROM Students
WHERE GraduationYear = 1994
ORDER BY GradePointAverage ASC
ASC e DESC specificano il senso dell'ordinamento (ascending o descending); il valore che segue TOP deve
essere un intero senza segno.
Operazioni di JOIN, LEFT JOIN, RIGHT JOIN in SQL
» INNER JOIN
Combina records da due tables quando hanno valori corrispondenti in un campo comune.
Sintassi:
FROM table1 INNER JOIN table2 ON table1.field1 compareop table2.field2
table1, table2: inomi delle Tables dalle quali combinare i records.
field1, field2: inomi dei campi oggetto della join. Devono essere dello stesso data type e contenere la stessa
specie di dati, ma non è necessario che abbiano lo stesso nome.
compareop: un' operatore relazionale: "=," "<," ">," "<=," ">=," o "<>."
Note:
se si usano come campi di join campi Memo o OLE Object si causa un errore.
Tra due campi numerici è possibile la join solo se sono dello stesso tipo: per esempio AutoNumber
(Contatore) e Long sono dello stesso tipo; Single e Double non sono dello stesso tipo e si causa un errore.
La INNER JOIN combina i records da due Tables quando hanno valori corrispondenti in un campo comune
ad entrambe le Tables.
Potete usare INNER JOIN con le Tables Departments e Employees per ottenere tutti gli impiegati di ogni
dipartimento. Se invece volete tutti i dipartimenti (anche se non hanno alcun impiegato assegnato) o tutti gli
impiegati (anche se qualcuno non è assegnato ad alcun dipartimento), dovete usare una operazione LEFT
JOIN o RIGHT JOIN per creare una "outer join".
L'esempio mostra la join delle Tables Categories and Products tramite il campo CategoryID:
SELECT CategoryName, ProductName
FROM Categories INNER JOIN Products
ON Categories.CategoryID = Products.CategoryID
CategoryID è il campo di join, ma non è incluso nell'output della query non essendo incluso nella fields list
della SELECT; per includerlo aggiungere il nome del campo, ovvero Categories.CategoryID.
Si possono collegare più clausole ON in una JOIN usando la seguente sintassi:
SELECT fields
FROM table1 INNER JOIN table2
ON table1.field1 compopr table2.field1 AND
ON table1.field2 compopr table2.field2) OR
ON table1.field3 compopr table2.field3)]
Si possono nidificare le operazioni di JOIN usando la seguente sintassi:
SELECT fields
FROM table1 INNER JOIN
(table2 INNER JOIN [( ]table3
[INNER JOIN [( ]tablex [INNER JOIN ...)]
ON table3.field3 compopr tablex.fieldx)]
ON table2.field2 compopr table3.field3)
ON table1.field1 compopr table2.field2
Una LEFT JOIN o una RIGHT JOIN possono essere nidificate dentro una INNER JOIN, ma una INNER
JOIN non può essere nidificata entro una LEFT JOIN o una RIGHT JOIN.
» LEFT JOIN, RIGHT JOIN
Sintassi:
FROM table1 [ LEFT | RIGHT ] JOIN table2
ON table1.field1 compopr table2.field2
La sintassi è identica a quella della INNER JOIN.
Note:
se si usano come campi di join campi Memo o OLE Object si causa un errore.
L'operatore LEFT JOIN crea una join esterna sinistra, ovvero include tutti i records della prima (left) Table
anche se non c'è corrispondenza con i records nella seconda (right) Table.
L'operatore RIGHT JOIN crea una join esterna destra, ovvero include tutti i records della seconda (right)
Table anche se non c'è corrispondenza con i records nella prima (left) Table.
Per esempio useremo LEFT JOIN con le Tables Departments (left) e Employees (right) per ottenere tutti i
dipartimenti, inclusi quelli che non hanno impiegati assegnati; per ottenere invece tutti gli impiegati, inclusi
quelli non assegnati ad un dipartimento, useremo RIGHT JOIN.
LEFT JOIN o RIGHT JOIN possono essere nidificate all'interno di INNER JOIN, ma INNER JOIN non può
essere nidificata entro LEFT JOIN o RIGHT JOIN; per la sintassi di nidificazione vale quanto detto per
INNER JOIN, così come per le clausole ON multiple ed i campi Memo o OLE Object.
Altri comandi SQL
» SELECT .... INTO Statement
Query di creazione di una Table.
Sintassi:
SELECT field1[, field2[, ...]] INTO newtable [IN externaldatabase]
FROM source
Crea una nuova Table, eventualmente in un Db esterno, avente i campi specificati nella fields list; non può
essere utilizzata per aggiungere records ad una Table esistente.
» DELETE Statement
Query di cancellazione dei records di una Table che soddisfano un determinato criterio.
Sintassi:
DELETE [table.*]
FROM table
WHERE criteria
DELETE serve per cancellazioni multiple; inoltre se la Table da cui si cancella è posta in una relazione unoa-molti con altre Tables vengono cancellati anche i records relativi dalle altre Tables.
Per esempio se la Table "Customers" è in relazione uno-a-molti con la Table "Orders" cancellando alcuni
clienti da Customers si otterrà anche la cancellazione degli ordini relativi da Orders.
» INSERT INTO Statement
Query di inserimento di uno o più records in una Table (append query).
Sintassi:
(Multiple-record append query):
INSERT INTO target [(field1[, field2[, ...]])] [IN externaldatabase]
SELECT [source.]field1[, field2[, ...]
FROM tableexpression
(Single-record append query):
INSERT INTO target [(field1[, field2[, ...]])]
VALUES (value1[, value2[, ...])
Qualora la Table di destinazione abbia un campo Contatore (Autonumber) non inserire tale campo nella
fields list; la clausola FORM può anche contenere JOIN di più Tables.
» UPDATE, DELETE e INSERT Statements
Le applicazioni SQL-based effettuano gli aggiornamenti delle Tables con l'esecuzione dei comandi UPDATE,
DELETE ed INSERT. Questi comandi fanno parte del "Minimum SQL grammar conformance level" e devono
essere supportati da tutti i drivers e da tutti i Database.
Sintassi:
UPDATE table-name
SET column-identifier = {expression | NULL}
[, column-identifier = {expression | NULL}]...
[WHERE search-condition]
DELETE FROM table-name [WHERE search-condition]
INSERT INTO table-name [(column-identifier [, column-identifier]...)]
{query-specification | VALUES (insert-value [, insert-value]...)}
Questi comandi sono riportati a puro titolo esemplificativo non essendo utilizzabili nella programmazione
Visual Basic dove invece si ricorre in genere a Metodi applicati agli Oggetti.
Approfondimenti
» "Interactive/On-line SQL Tutorial" di F. Torres
» "SQL Index"
» "Interactive SQL" di Jean Anderson
» "Database glossary"
» "Starting a Career in Databases"
» "A pratical SQL Tutorial"