Concludiamo la progettazione del nostro caso di studio con la progettazione fisica. Non assumeremo l'uso di alcun DBMS specifico ma faremo riferimento ad SQL standard. In particolare questo ci impedirà di definire in SQL le strutture di memorizzazione per le tabelle e gli indici. Lo schema fisico della base di dati TeatroSQL, in tutte le sue componenti (domini, tabelle, asserzioni, trigger e viste) è il seguente:
create schema TeatroSQL
{
create domain tipoBiglietto as varchar(8)
default "Intero"
check (tipoBiglietto in ('Intero', 'Ridotto', 'Studenti'))
create domain mansione as varchar(5)
check (mansione in ('CA', 'POD', 'CUSRP', 'ACF'))
create domain tipoProduttore as varchar(7)
check (tipoProduttore in ('interno', 'esterno'))
create table teatro
(
nome varchar(20) primary key,
telefono varchar(15),
fax varchar(15),
indirizzo varchar(40) not null,
email varchar(30),
url varchar(30)
)
create table biglietteria
(
nome varchar(20) primary key,
indirizzo varchar(40) not null,
email varchar(30),
telefono varchar(15),
teatro varchar(20) foreign key references teatro(nome)
on update cascade on delete set null
)
create table orario
(
biglietteria varchar(20),
giorno varchar(10),
inizio time,
fine time,
primary key(biglietteria, giorno, inizio),
foreign key biglietteria references biglietteria(nome)
on update cascade on delete cascade,
)
create table notizia
(
data date,
ora time,
oggetto varchar(100),
testo CLOB,
primary key(data, ora, oggetto)
)
create table newsletter
(
teatro varchar(20),
data date,
ora time,
oggetto varchar(100),
primary key(teatro, data, ora, oggetto),
foreign key teatro references teatro(nome)
on update cascade on delete cascade,
foreign key (data, ora, oggetto) references notizia(data, ora, oggetto)
on update cascade on delete cascade
)
create table dipendente
(
cf char(16) primary key,
nome varchar(20) not null,
cognome varchar(20) not null,
dataDiNascita date,
luogoDiNascita varchar(20),
residenza varchar(30),
telefonoFisso varchar(15),
telefonoMobile varchar(15),
email varchar(30)
)
create table lavoro
(
teatro varchar(20),
dipendente char(16),
dataAssunzione date,
ruolo mansione,
cda boolean,
primary key(teatro, dipendente),
foreign key teatro references teatro(nome)
on update cascade on delete cascade,
foreign key dipendente references dipendente(cf)
on update cascade on delete cascade,
constraint RA1 check (
cda = false or
(year(current_date) - year(dataAssunzione) > 10) or
(year(current_date) - year(dataAssunzione) = 10 and
month(current_date) > month(dataAssunzione)) or
(year(current_date) - year(dataAssunzione) = 10 and
month(current_date) = month(dataAssunzione) and
day(current_date) >= day(dataAssunzione))
)
)
create table stipendio
(
dipendente char(16),
inizio date,
importo decimal(6,2),
primary key(dipendente, inizio),
foreign key dipendente references dipendente(cf)
on update cascade on delete cascade
)
create table spazio
(
nome varchar(20) primary key,
indirizzo varchar(40) not null,
pianta varchar(20),
capienza smallint
)
create table luogo
(
teatro varchar(20),
spazio varchar(20),
primary key(teatro, spazio),
foreign key teatro references teatro(nome)
on update cascade on delete cascade
foreign key spazio references spazio(nome)
on update cascade on delete cascade
)
create table stagione
(
nome varchar(20),
biennio char(9),
teatro varchar(20),
primary key(nome, biennio),
foreign key teatro references teatro(nome)
on update cascade on delete set null
)
create table spettacolo
(
titolo varchar(40) primary key,
descrizione CLOB,
annoProduzione char(4),
)
create table messaInScena
(
data date,
ora time,
spazio varchar(20),
spettacolo varchar(40),
postiDisponibili smallint,
prezzoIntero decimal(5,2),
prezzoRidotto decimal(5,2),
prezzoStudenti decimal(5,2),
primary key (data, ora, spazio),
foreign key spazio references spazio(nome)
on update cascade on delete set null,
foreign key spettacolo references spettacolo(titolo)
on update cascade on delete set null,
constraint RA3-1 check (postiDisponibili >= 0)
)
create table materiale
(
file varchar(30) primary key,
tipo varchar(10),
dimensione integer,
dataSpettacolo date,
oraSpettacolo time,
spazioSpettacolo varchar(20),
foreign key (dataSpettacolo, oraSpettacolo, spazioSpettacolo) references
messaInScena(data, ora, spazio)
on update cascade on delete set null
)
create table prenotazione
(
dataSpettacolo date,
oraSpettacolo time,
spazioSpettacolo varchar(20),
numero smallint,
data date,
ora time,
posto varchar(5),
tipo tipoBiglietto,
prezzo decimal(5,2),
primary key(spettacolo, numero),
foreign key (dataSpettacolo, oraSpettacolo, spazioSpettacolo) references
messaInScena(data, ora, spazio)
on update cascade on delete cascade
)
create table commento
(
autore varchar(20),
data date,
ora time,
testo CLOB,
dataSpettacolo date,
oraSpettacolo time,
spazioSpettacolo varchar(20),
primary key (autore, data, ora),
foreign key (dataSpettacolo, oraSpettacolo, spazioSpettacolo) references
messaInScena(data, ora, spazio)
on update cascade on delete set null
)
create table risposta
(
autoreRisposta varchar(20),
dataRisposta date,
oraRisposta time,
autore varchar(20),
data date,
ora time,
primary key (autoreRisposta, dataRisposta, oraRisposta),
foreign key (autoreRisposta, dataRisposta, oraRisposta) references
commento(autore, data, ora)
on update cascade on delete cascade,
foreign key (autore, data, ora) references
commento(autore, data, ora)
on update cascade on delete cascade
)
create table produzione
(
produttore varchar(20),
spettacolo varchar(40),
primary key(produttore, spettacolo),
foreign key produttore references produttore(nome)
on update cascade on delete cascade
foreign key spettacolo references spettacolo(titolo)
on update cascade on delete cascade
)
create table produttore
(
nome varchar(20) primary key,
tipo tipoProduttore,
indirizzo varchar(40),
telefono varchar(15),
email varchar(30),
)
create table interprete
(
nome varchar(20),
ruolo varchar(15),
cv CLOB,
primary key(nome)
)
create table cast
(
spettacolo varchar(40),
interprete varchar(20),
primary key(spettacolo, interprete),
foreign key spettacolo references spettacolo(titolo)
on update cascade on delete cascade
foreign key interprete references interprete(nome)
on update cascade on delete cascade
)
crate assertion RA2 check (
not exists (
select stagione.teatro, stagione.nome, stagione.biennio, count(*)
from (stagione join proposta on
(stagione.nome = proposta.nomeStagione) and
(stagione.biennio = proposta.biennioStagione))
join produzione on
(proposta.spettacolo = produzione.spettacolo) and
(stagione.teatro = produzione.produttore)
group by stagione.teatro, stagione.nome, stagione.biennio
having count(*) > 2
)
)
crate trigger RA3-2
after insert on prenotazione
for each row
update messaInScena
set postiDisponibili = postiDisponibili - 1
where (data = New.dataSpettacolo and
ora = New.oraSpettacolo
spazio = New.spazioSpettacolo)
crate trigger RA3-3
after delete on prenotazione
for each row
update messaInScena
set postiDisponibili = postiDisponibili + 1
where (data = Old.dataSpettacolo and
ora = Old.oraSpettacolo
spazio = Old.spazioSpettacolo)
crate trigger RA3-4
after insert on messaInScena
for each row
update messaInScena
set postiDisponibili = (select capienza
from spazio
where nome = New.spazio)
where (data = New.data and
ora = New.ora
spazio = New.spazio)
crate trigger RA4-1
after insert on messaInScena
for each row
update messaInScena
set New.prezzoRidotto = New.prezzoIntero * 0.8
where (data = New.data and
ora = New.ora
spazio = New.spazio)
crate trigger RA4-2
after insert on messaInScena
for each row
update messaInScena
set New.prezzoStudenti = New.prezzoIntero * 0.5
where (data = New.data and
ora = New.ora
spazio = New.spazio)
crate trigger RA4-3
after update of prezzoIntero on messaInScena
for each row
update messaInScena
set New.prezzoRidotto = New.prezzoIntero * 0.8
where (data = New.data and
ora = New.ora
spazio = New.spazio)
crate trigger RA4-4
after update of prezzoIntero on messaInScena
for each row
update messaInScena
set New.prezzoStudenti = New.prezzoIntero * 0.5
where (data = New.data and
ora = New.ora
spazio = New.spazio)
crate trigger RA5-1
after insert on prenotazione
for each row
when (tipo = "Intero")
update prenotazione
set New.prezzo = (select prezzoIntero
from messaInScena
where (data = New.dataSpettacolo and
ora = New.oraSpettacolo and
spazio = New.spazioSpettacolo))
where (dataSpettacolo = New.dataSpettacolo and
oraSpettacolo = New.oraSpettacolo and
spazioSpettacolo = New.spazioSpettacolo and
numero = New.numero)
crate trigger RA5-2
after insert on prenotazione
for each row
when (tipo = "Ridotto")
update prenotazione
set New.prezzo = (select prezzoRidotto
from messaInScena
where (data = New.dataSpettacolo and
ora = New.oraSpettacolo and
spazio = New.spazioSpettacolo))
where (dataSpettacolo = New.dataSpettacolo and
oraSpettacolo = New.oraSpettacolo and
spazioSpettacolo = New.spazioSpettacolo and
numero = New.numero)
crate trigger RA5-1
after insert on prenotazione
for each row
when (tipo = "Studenti")
update prenotazione
set New.prezzo = (select prezzoStudenti
from messaInScena
where (data = New.dataSpettacolo and
ora = New.oraSpettacolo and
spazio = New.spazioSpettacolo))
where (dataSpettacolo = New.dataSpettacolo and
oraSpettacolo = New.oraSpettacolo and
spazioSpettacolo = New.spazioSpettacolo and
numero = New.numero)
create view spettacoli(teatro, nomeStagione, biennioStagione,
titolo, descrizione, data, ora, spazio, indirizzo, postiDisponibili
prezzoIntero, prezzoRidotto, prezzoStudenti) as
select St.teatro, St.nomeStagione, St.biennioStagione, Spe.titolo,
Spe.descrizione, Sce.data, Sce.ora, Sce.spazio, Spa.indirizzo,
Sce.postiDisponibili, Sce.prezzoIntero, Sce.prezzoRidotto, Sce.prezzoStudenti
from stagione St, proposta P, spettacolo Spe, messaInScena Sce, spazio Spa
where (St.nome = P.nomeStagione) and
(St.biennio = P.biennioStagione) and
(P.spettacolo = Spe.titolo) and
(Spe.titolo = Sce.spettacolo)
(Sce.spazio = Spa.nome)
order by St.teatro, St.nomeStagione, St.biennioStagione, Sce.data
create view attori(teatro, nomeStagione, biennioStagione, nome, ruolo, cv) as
select St.teatro, St.nomeStagione, St.biennioStagione, I.nome, I.ruolo, I.cv
from stagione St, proposta P, spettacolo Sp, cast C, interprete I
where (St.nome = P.nomeStagione) and
(St.biennio = P.biennioStagione) and
(P.spettacolo = Sp.titolo) and
(Sp.titolo = C.spettacolo) and
(C.interprete = I.nome)
order by St.teatro, St.nomeStagione, St.biennioStagione, I.nome, I.ruolo
create view dipendenti(teatro, cognome, nome, cf, età, telefonoFisso,
telefonoMobile, email, stipendio, dataDiAssunzione, ruolo, cda) as
select L.teatro, D.cognome, D.nome, D.cf,
case
when ((month(current_date) > month(D.dataDiNascita)) or
(month(current_date) = month(D.dataDiNascita) and
day(current_date) >= day(D.dataDiNascita)))
then year(current_date) - year(D.dataDiNascita)
else year(current_date) - year(D.dataDiNascita) - 1
end,
D.telefonoFisso, D.telefonoMobile, D.email, S.stipendio,
L.dataDiAssunzione, L.ruolo, L.cda
from lavoro L, dipendente D, stipendio S
where (L.dipendente = D.cf) and (S.dipendente = D.cf) and
(S.inizio = (select max(S2.inizio)
from stipendio S2
where S2.dipendente = D.cf))
order by L.teatro, D.cognome, D.nome
create view statScena(spettacolo, paganti, affluenza, incasso) as
select Sce.spettacolo, count(*), count(*) / Spa.capienza, sum(P.prezzo)
from messaInScena Sce, spazio Spa, prenotazione P
where (Sce.spazio = Spa.nome) and
(P.dataSpettacolo = Sce.data) and
(P.oraSpettacolo = Sce.ora) and
(P.spazioSpettacolo = Sce.spazio)
group by Sce.data, Sce.ora, Sce.spazio
create view statSpettacolo(spettacolo, paganti, affluenza, incasso) as
select Spe.titolo, avg(Sce.paganti), avg(Sce.affluenza), avg(Sce.incasso)
from spettacolo Spe, statScena Sce
where (Spe.titolo = Sce.spettacolo)
group by Spe.titolo
}
Seguono alcune osservazioni:
crate trigger RA3-1
before insert on prenotazione
for each row
when (0 = (select postiDisponibili
from messaInScena
where (data = New.dataSpettacolo and
ora = New.oraSpettacolo
spazio = New.spazioSpettacolo)))
rollback("Posti esauriti")
Esso controlla, prima dell'inserimento della prenotazione nella base di dati, che vi siano posti disponibili. Se non ve ne sono, annulla l'operazione di inserimento e avvisa l'utente che i posti sono esauriti. Si noti che la soluzione funziona se RA3-1 viene eseguito prima di RA3-2 (di solito è così in quanto RA3-1 è di tipo before e RA3-2 è di tipo after).
Mostriamo infine l'implementazione delle transazioni tipiche di interrogazione (quelle di inserimento dipendono dai dati da inserire). Alcune di queste transazioni fanno uso delle viste definite nello schema:
select C.autore, C.data, C.ora, C.testo
from messaInScena S, commento C
where (S.data = C.dataSpettacolo) and
(S.ora = C.oraSpettacolo) and
(S.spazio = C.spazioSpettacolo) and
(S.titolo = 'Gli straccioni')
select News.nome, N.data, N.ora, N.oggetto, N.testo
from notizia N, newsletter News
where (N.data = News.data) and
(N.ora = News.ora) and
(N.oggetto = News.oggetto) and
(News.teatro = 'CSS')
select O.biglietteria, O.giorno, O.inizio, O.fine from biglietteria B, orario O where (B.nome = O.biglietteria) and (B.teatro = 'CSS') order by O.biglietteria
select P.nomeStagione, P.biennioStagione, avg(SS.paganti) as mediaPaganti,
avg(SS.affluenza) as mediaAffluenza, avg(SS.incasso) as mediaIncasso
from statisticaSpettacolo SS, proposta P
where (SS.spettacolo = P.spettacolo)
group by P.nomeStagione, P.biennioStagione
select SS.spettacolo, SS.paganti
from statisticaSpettacolo SS, proposta P
where (SS.spettacolo = P.spettacolo) and
(P.nomeStagione = 'Contatto') and
(P.biennioStagione = '2006/2007')
order by SS.paganti
select SS.spettacolo, SS.affluenza
from statisticaSpettacolo SS, proposta P
where (SS.spettacolo = P.spettacolo) and
(P.nomeStagione = 'Contatto') and
(P.biennioStagione = '2006/2007')
order by SS.affluenza
select SS.spettacolo, SS.incasso
from statisticaSpettacolo SS, proposta P
where (SS.spettacolo = P.spettacolo) and
(P.nomeStagione = 'Contatto') and
(P.biennioStagione = '2006/2007')
order by SS.incasso