Esempio Tesina - Progetto Atena

annuncio pubblicitario
UNIVERSITÁ DEGLI STUDI DI MODENA
CORSO DI LAUREA IN INGEGNERIA INFORMATICA
Corso di BASI DI DATI
Anno accademico 1997-98
di
Davide Lenzi
&
Christian Pezzin
SOMMARIO
INTRODUZIONE......................................................................................................................................................3
SPECIFICHE DEI REQUISITI ................................................................................................................................4
ANALISI DELLE SPECIFICHE.......................................................................................................................................4
DEFINIZIONI VARIE ..............................................................................................................................................7
ANALISI DEI REQUISITI .......................................................................................................................................8
PROGETTO DELLO SCHEMA E/R ................................................................................................................................8
CONSIDERAZIONI SU UN PROBLEMA DI DATO DERIVATO ...........................................................................................11
E/R COMPLETO ......................................................................................................................................................13
IMPLEMENTAZIONE DELLA BASE DI DATI...................................................................................................14
TRADUZIONE NEL MODELLO RELAZIONALE .............................................................................................................14
IMPLEMENTAZIONE DELLA BASE DI DATI IN SQL .....................................................................................17
IMPLEMENTAZIONE DI ULTERIORI VINCOLI DI PROGETTO IN SQL..............................................................................17
Vincolo: validità delle età immesse per una nuova Categoria ............................................................................17
Vincolo: impedire modifiche illegali per le tuple della tabella Categoria ...........................................................18
Vincolo: eliminazione di tuple inutili .................................................................................................................19
Inserimento e cancellazione automatica dei dati in ATLPartecipa......................................................................19
Modalità TROFEO di una manifestazione .........................................................................................................20
Indici................................................................................................................................................................21
STRUTTURA DELL’APPLICAZIONE.................................................................................................................22
DESCRIZIONE DEI PRINCIPALI FORM ........................................................................................................................25
Form: Gestore singola manifestazione ..............................................................................................................25
Form: Gestore Iscrizioni Atleti-Gare.................................................................................................................27
IMPORTAZIONE DI DATI DALLA VECCHIA VERSIONE GGN ....................................................................29
BIBLIOGRAFIA .....................................................................................................................................................30
APPENDICE A ........................................................................................................................................................31
INTRODUZIONE
Sul finire degli anni ’80, nella maggior parte delle realtà del mondo natatorio, non esisteva in
sostanza alcun supporto informatico alla gestione delle manifestazioni sportive. Manifestazioni con
centinaia d’atleti erano gestite manualmente da una ventina di persone che dovevano fare i salti
mortali per registrare ed ordinare le classifiche, quindi i risultati definitivi erano noti spesso uno o
due giorni dopo la manifestazione e venivano spediti per posta ordinaria a tutte le società
interessate, portando il ritardo complessivo a circa una settimana.
Un primo tentativo di risolvere il problema venne proposto da alcuni giovani programmatori con
una gran passione per il nuoto, che implementarono una piccola applicazione nota come GGN
(Gestione Gare Nuoto) funzionante in sistemi DOS anche su hardware obsoleto come gli 8086
dell'Intel. L’applicazione ebbe molto successo, ed oggi è la più diffusa nella nostra regione.
Con il passare degli anni, però, la semplice struttura (un'unica tabella!) del GGN ha cominciato a
mostrare la corda: manifestazioni divise in concentramenti, necessità di usare insiemi di categorie,
archivio delle prestazioni degli atleti, ad esempio, erano tutte funzionalità non supportate. Essendo
il GGN un'applicazione DOS non forniva supporto nemmeno per tutte le nuove stampanti che
comparivano sul mercato.
Il fattore che ha determinato la nascita del nostro progetto, però, è stata anche la vulnerabilità del
GGN al celeberrimo bug dell’anno 2000: l’applicazione memorizzava, infatti, le date delle
manifestazioni e le date di nascita degli atleti con solo due cifre (le date di nascita servono per
determinare le categorie di appartenenza degli atleti), da cui l'ormai noto bug Y2K.
E' così dunque che nasce il GGN 2000. Un'applicazione pienamente compatibile con i nuovi
sistemi basati su Windows 95/98/NT e con funzione di importazione dei dati creati con la vecchia
versione del GGN.
3
SPECIFICHE DEI REQUISITI
Le società sportive di NUOTO si vogliono dotare di un sistema informativo per la gestione dei dati
riguardanti gli atleti, le società, ed in modo particolare per la gestione delle manifestazioni sportive.
Analisi delle specifiche
Nel mondo delle manifestazioni natatorie esistono varie entità da modellare, due di queste sono:
società (descritte da un nome univoco) e federazioni (descritte da un nome e da una sigla univoca).
Una manifestazione è descritta dalle date in cui si tiene, dal luogo, dal tipo di regolamento adottato,
dalle categorie d’età degli atleti ammesse a partecipare. Ogni società ha vari atleti iscritti.
Una manifestazione è organizzata da una delle società; nel caso in cui la società organizzi la
manifestazione per conto di una federazione allora la manifestazione è detta “ufficiale” altrimenti è
detta “non ufficiale” e può utilizzare un qualunque insieme di categorie. Un atleta per appartenere
ad una categoria (e quindi poter disputare gare di quella categoria) deve essere del sesso e dell’età
richiesta, quindi una categoria è caratterizzata da un nome (non necessariamente univoco) dal sesso
dei suoi atleti, da un’età minima e un’età massima. Federazioni diverse solitamente utilizzano, e
permettono, solo l’utilizzo di determinati insiemi di categorie. Ad esempio, le categorie utilizzate
dalla FIN (Federazione Italiana Nuoto) differiscono per il tipo di età e per i nomi da quelle utilizzate
dal CSI (Centro Sportivo Italiano) o dalla UISP.
Una manifestazione può essere divisa in concentramenti, ossia può svolgersi in più impianti
differenti e in date diverse ma sempre relative ad un unico anno agonistico. Ogni impianto ha
ovviamente sue caratteristiche proprie come il numero di corsie e la lunghezza della vasca. In ogni
concentramento si possono svolgere discipline differenti di categorie differenti oppure possono
esservi gare dello stesso tipo.
Le discipline sono caratterizzate da una distanza da “percorrere” e da uno stile di nuoto;
l’associazione di una disciplina a una categoria di atleti è detta gara.
Un atleta può partecipare ad una o più gare all’interno della stessa manifestazione, ed ogni
partecipazione è descritta da un cartellino, dove compaiono il tempo di iscrizione e la prestazione
ottenuta durante la gara.
Gestione delle manifestazioni
In certe date vengono organizzate delle manifestazioni. Ad ogni manifestazione sono ammesse
determinate categorie di atleti. Le categorie variano da gara a gara in base al regolamento utilizzato.
L’organizzatore decide, di volta in volta, il set di gare a cui una categoria può partecipare.
Esempio di Categorie:
Categoria
Pulcini
Giovanissimi
Ragazzi
Allievi
Assoluti
Femmine Maschi
89-90
90-91
87-88
88-89
85-86
86-87
83-84
84-85
82 e prec. 83 e prec.
(I numeri si riferiscono agli anni di nascita degli atleti)
Una volta definite le gare di un concentramento, devono essere preparate le batterie, in base al
4
tempo di iscrizione di ogni atleta a quella gara.
Lo schema di realizzazione delle batterie si basa sul seguente algoritmo: l’atleta con il miglior
tempo va posto nella corsia “centrale” della vasca e gareggia nell’ultima batteria, l’atleta con il
secondo tempo viene posto nella corsia centrale della penultima batteria e così via. Le piscine
solitamente hanno 6, 8 o 10 corsie e le corsie dette “centrali” sono per convenzione la numero 3, 4 e
5; esistono comunque piscine con un numero non standard di corsie di cui bisogna tenere conto.
Una volta che tutte le corsie centrali di tutte le batterie sono state occupate si procede a riempire le
corsie immediatamente adiacenti.
Si veda l’esempio: supponiamo di avere una gara in una piscina a 6 corsie, alla quale siano iscritti i
seguenti atleti con i relativi tempi di iscrizione:
ATLETA
A
B
C
D
E
F
G
H
I
J
K
L
M
TEMPO
1’01’’
1’02’’
1’03’’
1’04’’
1’05’’
1’06’’
1’07’’
1’08’’
1’09’’
1’10’’
1’11’’
1’12’’
1’13’’
Nel programma della manifestazione, che deve essere stampato dall’applicazione, le batterie
devono essere riportate nel seguente ordine di esecuzione:
Batteria 1:
CORSIA
2
3
4
5
ATLETA
I
C
F
L
SOCIETA’SPORTIVA
YYY
ZZZ
XXX
ZZZ
TEMPO ISCRIZIONE
1’09’’
1’03’’
1’06’’
1’12’’
ATLETA
H
B
E
K
SOCIETA’SPORTIVA
XXX
YYY
ZZZ
YYY
TEMPO ISCRIZIONE
1’08’’
1’02’’
1’05’’
1’11’’
Batteria 2:
CORSIA
2
3
4
5
5
Batteria 3:
CORSIA
1
2
3
4
5
ATLETA
M
G
A
D
J
SOCIETA’SPORTIVA
YYY
ZZZ
XXX
YYY
XXX
TEMPO ISCRIZIONE
1’13’’
1’07’’
1’01’’
1’04’’
1’10’’
Si noti che la distribuzione degli atleti è avvenuta in modo proporzionale, ovvero partendo dalle
corsie centrali e passando di volta in volta a quelle più esterne. Inoltre il numero delle batterie e dei
relativi partecipanti deve essere ottimizzato: se ci sono 13 atleti iscritti a una gara che si svolge in
un impianto a 6 corsie, non si devono generare 3 batterie da 6,6,1 partecipanti quanto invece 4,4,5.
A mano a mano che le varie batterie vengono disputate i tempi affluiscono all’applicazione che
dovrà provvedere alla stampa delle classifiche per ogni gara. L’inserimento dei tempi avviene in
contemporanea allo svolgimento delle batterie, in una tabella in cui compaiono tutti gli iscritti a
quella particolare gara, indipendentemente dalla batteria di appartenenza. Se qualcuno non ha
partecipato, il suo tempo non viene immesso e quindi non considerato nella classifica finale della
gara. Inoltre, deve essere possibile squalificare atleti scorretti o segnalare eventuali ritirati durante la
gara.
Nel caso una manifestazione sia un TROFEO, si deve poter specificare uno schema di punteggi
adattabile secondo le esigenze dell’organizzatore. Ad ogni società sono attribuiti, per esempio, 10
punti per ogni atleta 1° classificato, 8 punti per ogni atleta 2° classificato e così via. L’applicazione
deve poter calcolare e memorizzare la graduatoria delle varie società.
Altre funzioni offerte
Oltre a fornire normali strumenti di archivio per consultare i risultati di manifestazioni avvenute in
anni agonistici passati, l’applicazione deve permettere di risalire alle varie prestazioni degli atleti e
fornire i rispettivi record per le varie discipline.
Un'altra caratteristica presente è la possibilità di importare dati riguardanti atleti, manifestazioni e
gare dal formato dell'applicazione GGN di cui abbiamo parlato nell'introduzione. Essendo scritta in
un altro linguaggio e con altre caratteristiche implementative, questa funzione di conversione risulta
molto utile, considerando anche il fatto che nell'ambiente natatorio di molte piccole società, questo
formato di dati è molto diffuso.
Osservazioni finali
Trattandosi di un problema reale, queste specifiche descritte sono state ottenute in seguito a varie
interviste con il committente. La persona con cui abbiamo parlato era uno dei collaboratori alla
realizzazione della vecchia versione dell'applicativo, quindi ci ha potuto fornire utili informazioni
riguardo ad eventuali problemi di carattere implementativo a cui saremmo andati incontro. D'altro
canto, però, si è dovuto provvedere ad una fase di rielaborazione delle specifiche stesse per
eliminare tutte le possibili ambiguità presenti.
6
DEFINIZIONI VARIE
Presentiamo ora alcune definizioni e chiarimenti di eventuali termini presenti nella stesura del
testo, che possono risultare poco chiari o di difficile interpretazione.
ANNO AGONISTICO: identifica l'anno in cui si svolge una manifestazione; ad es. se l'anno
agonistico è 1998/99, ciò vuol dire che le manifestazioni potranno svolgersi fra il 1° settembre del
1998 e il 31 agosto del 1999.
CARTELLINO: entità (nel mondo reale è un foglietto di cartoncino) che rappresenta l'avvenuta
partecipazione di un atleta ad una gara. Riporta tutte le informazioni importanti relative alla gara e
all'atleta stesso, compreso il tempo della prestazione ottenuta. Può accadere di dover specificare, al
posto del tempo dei valori diversi:
RITIRATO
- L’atleta è sceso in acqua è regolarmente partito ma ha dovuto interrompere
la gara.
SQUALIFICATO
- L’atleta ha infranto delle regole imposte dalla federazione e la sua
prestazione viene annullata.
PRIMAGARA
- Questo valore è valido solo per il tempo di iscrizione: indica che un atleta
non ha mai partecipato in passato a una gara come quella specificata.
CONCENTRAMENTO: identifica l'impianto in cui si svolge un insieme di gare appartenenti alla
stessa manifestazione. Le manifestazioni solitamente vengono divise in concentramenti quando
richiamano atleti da tutta la regione, per minimizzare gli spostamenti di centinaia di persone si
creano diversi concentramenti.
GARA: evento singolo all'interno di una manifestazione, caratterizzata da una disciplina ed una
categoria (ad es. 50 m. stile libero Seniores).
MANIFESTAZIONE: evento sportivo, organizzato da una società, alla quale partecipano vari atleti;
può essere suddivisa in più concentramenti.
SCHEMA DI CATEGORIA: gruppo di categorie diverse che una federazione può ammettere nelle
manifestazioni ufficiali.
VASCA CORTA: piscina da 25 metri.
VASCA LUNGA: piscina da 50 metri.
MISTI: tipologia di stile natatorio in cui si devono percorrere le vasche seguendo i quattro stili base
del nuoto, secondo la sequenza: delfino, dorso, rana, stile libero. Es.: 100 metri misti in vasca corta
equivalgono a 25m Delfino seguiti da 25m dorso, 25m rana e 25m stile in sequenza.
Nota sui tempi ottenuti in gara: considerando il fatto che la lunghezza di una vasca può essere di 25
o 50 metri, i tempi ottenuti a parità di distanza percorsa dall’atleta in una vasca da 50 metri sono
tenuti in diversa considerazione da quelli ottenuti in vasca corta (in media un atleta che gareggia sui
100m stile libero ottiene tempi più bassi di 1-2 secondi in vasca corta).
7
ANALISI DEI REQUISITI
Progetto dello schema E/R
Dalle specifiche del progetto si possono evidenziare alcune entità e associazioni fondamentali che
compongono un primo “schema scheletro”:
CODSOC
CODATL
NOMES
SOCIETA
ANNO_NASCITA
ISCRITTO
(0,1)
(0,N)
(0,N)
ATLETI
(0,N)
ORGANIZZA
SIGLAF
NOMEF
FEDERAZIONE
(1,1)
(0,1)
(0,N)
ANNOAG.
DENOMINAZIONE
SIGLA
CODMAN
MANIFESTAZIONI
(1,N)
DIVISAIN
TEMPOISCR
CARTELLINI
(1,1)
TEMPOGARA
NPROG
CONCENTRAMENTI
ANNOSTART
ANNOEND
(0,N)
NOME
SESSO
(0,N)
ISTITUISCE
CATEGORIA
(1,1)
(1,1)
DIST
DISCIPLINE (0,N)
(0,N)
(1,1) GARECONCENTRAMENTO
STILE
Questo schema non modella tutte le caratteristiche che dovrà avere lo schema finale ma fornisce
una buona base di partenza per la trattazione. (ATTENZIONE: gli attributi meno importanti sono
stai omessi - vedere la sezione dedicata alla traduzione in relazionale per la descrizione completa di
tutti gli attributi.)
L’entità ATLETI ha una associazione facoltativa con l’entità SOCIETÀ: un atleta in un dato istante
può essere iscritto ad un'unica società o a nessuna, questo per modellare il fatto che nella realtà un
atleta dopo un certo periodo si può ritirare dall’attività agonistica e i suoi dati ai fini dell’iscrizione
a nuove manifestazioni non devono più essere associati a nessuna delle società in cui ha militato in
passato.
Tutti gli atleti che non hanno una società di appartenenza dunque sono da intendersi come ritirati.
Nell’entità CATEGORIA gli attributi ANNOSTART, ANNOEND e SESSO indicano rispettivamente l’età minima,
massima ed il sesso che un atleta deve avere per appartenere a quella categoria.
I concentramenti possono avere varie gare associate; come si nota una gara è identificata una volta
fornite la disciplina, la categoria ed il concentramento in cui la gara ha luogo. Al termine di una
8
manifestazione devono essere forniti i risultati definitivi: tra tutte le gare che compaiono in
GARECONCENTRAMENTO relative alla manifestazione (escludendo i duplicati che hanno stessa disciplina
e categoria in concentramenti differenti) viene stilata un'unica classifica indipendente dal
concentramento.
Lo schema, per come è stato modellato, consente l’iscrizione di uno stesso atleta ad una data gara
in tutti i concentramenti (anche se solo una volta per ognuno); quando l’applicazione dovrà generare
la classifica definitiva considererà per ogni atleta solo il miglior tempo ottenuto in quella gara nei
vari concentramenti. Nella realtà questa situazione si verifica in manifestazioni come “Coppa
Olimpica” della FIN dove gli atleti hanno a disposizione due sessioni (una invernale e una
primaverile) per cercare di qualificarsi per le fasi finali cui accederanno solo gli atleti che avranno
ottenuto i migliori tempi generali.
Siccome le specifiche richiedono un minimo di funzionalità di archivio, e considerando il fatto che
un atleta con gli anni può cambiare società di appartenenza, se mantenessimo inalterato lo schema
visto si avrebbero delle anomalie. Ad es. l’atleta Davide Lenzi partecipa nel ’91 alla manifestazione
X sotto i colori della società “Centese Nuoto” e nel ’93 si trasferisce alla società “CDR Modena”.
Se venisse fatta ora una ricerca di archivio sul Database risulterebbe che Davide Lenzi ha
partecipato alla manifestazione X per la sua nuova società e non vi sarebbe traccia della passata
militanza nella Centese Nuoto.
CODATL
CODSOC
Per supportare le ricerche di archivio è stata introdotta
l’associazione ATLPARTECIPA che permette di reperire la
ATLETI
SOCIETA
società per cui un dato atleta ha partecipato a una data
(0,N)
manifestazione. L’associazione tra ATLETA e SOCIETÀ
identifica dunque solo la società cui un atleta è iscritto
ATLPARTECIPA
(0,N)
correntemente.
Per non appesantire troppo lo schema E/R non abbiamo
rappresentato il vincolo che ad una manifestazione un
atleta può parteciparvi sotto i colori di un’unica società, e
(0,N)
sarebbe stato più corretto reificare ATLPARTECIPA (nella
CODMAN
traduzione in relazionale ne è stato comunque tenuto
MANIFESTAZIONI
conto).
Nelle specifiche è richiesto che le categorie siano
SIGLAF
organizzate in insiemi (detti schemi) che possono
essere utilizzati da una o più federazioni. Poiché i
FEDERAZIONE
regolamenti cambiano nel tempo non è detto che un
(1,N)
certo schema di categorie rimanga sempre associato
ad una federazione. Può accadere infatti che in un
CODMAN
FEDAMMETTE
MANIFESTAZIONI
dato istante uno schema che in passato sia stato
(1,1)
utilizzato per varie manifestazioni per la federazione
X ora non abbia alcuna federazione associata.
Tuttavia sempre per questioni di archivio è
(0,N)
TITOLO
PREVEDE
(0,N)
necessario mantenerlo nel Database (ecco il perché
SCHEMACATEGORIA
della partecipazione facoltativa in FEDAMMETTE). IDSC
(1,N)
Inoltre in una data manifestazione ufficiale devono
essere utilizzabili solo le categorie presenti in uno
(1,1)(0,N)
schema specificato; potendo esistere anche
CATEGORIA
manifestazioni non ufficiali, però, non era utile
impostare l’E/R in modo che forzasse il rispetto di questo vincolo. In questo caso è l’applicazione
che si incarica di far rispettare o meno il vincolo. Si è quindi provveduto all’introduzione di una
nuova entità SCHEMACATEGORIA associata a FEDERAZIONE, CATEGORIA e MANIFESTAZIONE come indicato
nello schema a lato).
9
Un'ultima funzionalità da supportare è la gestione di un trofeo. In questo tipo di manifestazioni, ad
ogni atleta viene assegnato un punteggio sulla base del suo piazzamento in classifica, quindi per
ogni società vengono sommati tutti i punti ottenuti dai propri atleti. Lo schema dei punteggi da
assegnare alle varie posizioni può variare a seconda dei desideri dell’organizzatore della
manifestazione. Sono state dunque introdotte le entità TROFEO come subset di MANIFESTAZIONI e
SCHEMAPUNTEGGI associate tra loro come nello schema qui sotto. Gli attributi di SCHEMAPUNTEGGI
stanno ad indicare i punti attribuiti agli atleti dal 1° al 4° posto di ogni gara e quanti punti togliere
per ogni posizione successiva (SCALARE) sino a raggiungere il punteggio minimo (PUNTIMIN).
Solitamente
a
una
manifestazione partecipano dai
PUNTEGGISOCIETA’
100 ai 200 atleti, e alcune
(0,N)
SOCIETA
manifestazioni particolarmente
PUNTI
grandi possono arrivare anche a
500; ogni atleta partecipa in
(0,N)
CODMAN
media
a
2
gare
per
TROFEO
MANIFESTAZIONI
manifestazione
e
questo
(1,1)
significa che l’associazione
CARTELLINI conterrà dalle 200
alle 400 tuple solo per una
manifestazione. In un anno si
svolgono
una
decina
di
manifestazioni
(stima
per
PUN2°
PUN4°
difetto) e dopo pochi anni si
(1,1)
capisce che la tabella che
SCHEMAPUNTEGGI PUNTIMIN
PUN1°
PUNTI1°-4°
conterrà le tuple dei cartellini
avrà una cardinalità molto
PUN3°
SCALARE
elevata (almeno per un PC).
Un vincolo stringente su cui il committente ha insistito molto è che le varie interrogazioni alla
base di dati siano eseguite in tempi “ragionevolmente brevi”, soprattutto nello stilare classifiche;
questo ci ha spinti a tenerne conto già a livello di E/R. La strada seguita è stata quella di
partizionare l’associazione CARTELLINI in 2: una partizione per i maschi ed una per le femmine,
sacrificando un po’ della semplicità dello schema a vantaggio di una minore cardinalità delle
tabelle, in modo che query anche molto complesse possano essere svolte in maniera più snella
anche da hardware modesto.
Dallo schema completo riportato a fine capitolo si nota che per poter reperire le informazioni circa
il nome dell’atleta, il nome della società di appartenenza e la prestazione conseguita in una data
manifestazione, sono necessari accessi a svariate entità, quindi un dimezzamento almeno teorico
delle tuple in CARTELLINI va a tutto vantaggio dei tempi di risposta.
CODSOC
10
Considerazioni su un problema di dato derivato
Durante la progettazione si è posto un piccolo problema di dato derivato: l’attributo PIAZZAMENTO
può essere calcolato sulla base della lista ordinata dei tempi ottenuti in gara.
CODATL
TEMPOISCR
GARECONCENTRAMENTO
TEMPOGARA
(0,N)
(0,N)
ATLETI
CARTELLINI
PIAZZAMENTO
Il piazzamento risulta molto utile per le query di classifica nonché per il calcolo della graduatoria
delle società essendo il punteggio ottenuto da ogni atleta una funzione diretta del piazzamento. Le
stime riportate nelle tabelle successive sono relative ad un'unica manifestazione.
Tabella dei volumi:
Concetto
Tipo Volume
Gareconcentramento
E
10
Atleti
E
150
Cartellini
R
300
Tabella delle operazioni:
Operazione
Tipo
Frequenza
Inserimento tempi gara
I
300/manifestazione
Calcolo classifica società
I
3/manifestazione
La stima di 3 calcoli di classifica per società in una manifestazione è prudenziale, nella gran parte
dei casi si calcolerà una volta sola.
Dalle cardinalità in gioco, dal tipo di operazioni richieste, e dalla loro frequenza sembrerebbe
inutile porsi il problema del dato derivato. In realtà dal punto di vista della realizzazione pratica
dell’applicazione avere già a disposizione il piazzamento nelle tabelle del database avrebbe
permesso una notevole semplificazione del codice non solo per le operazioni citate ma anche per il
reperimento della stessa classifica provvisoria, senza contare una sensibile riduzione del tempo di
sviluppo dell’applicazione. Volendo massimizzare le prestazioni abbiamo comunque scelto di non
usare l’attributo piazzamento.
Dal punto di vista dei costi di accesso nel calcolo della classifica delle società, l’impiego del dato
derivato porta ad una semplice scansione delle tuple in CARTELLINI per ottenere i punteggi
conseguiti da tutte le società (dunque 300 accessi in lettura). Senza il dato derivato invece si devono
reperire le classifiche delle singole gare e quindi scandirle.
In prima approssimazione, trascurando i costi di ordinamento, gli accessi in lettura per ogni
classifica sono mediamente 30. L’applicazione è costretta così a scandire nuovamente le singole
classifiche sommando il giusto punteggio alla società dell’atleta che compare in classifica; si
aggiungono dunque altri 30 accessi in lettura per ogni gara.
In realtà la scansione del risultato della prima query verrà con ogni probabilità eseguito tutto in
memoria centrale, questo e la presenza di cache faranno si che le letture effettive alla seconda
scansione avranno un peso molto inferiore rispetto alle prime, a rigore dovremmo considerarlo 2 o 3
ordini di grandezza in meno rispetto alle prime 30L. Tuttavia volendo fare un conto prudenziale
questo non verrà considerato.
11
In definitiva le letture per ottenere la classifica delle società escludendo i costi di ordinamento
sono 600:
(30L+30L)*10 gare = 600L (si vedano le tabelle seguenti).
Per l’operazione di inserimento invece si nota una notevole anomalia di aggiornamento (nel caso
di impiego del dato): ogni nuovo tempo immesso provoca l’aggiornamento dell’intera classifica con
accessi in scrittura a buona parte se non tutte le tuple interessate alla gara corrente: se ogni gara ha
in media 30 partecipanti allora in bisogna fare approssimativamente 15.5 accessi in scrittura (non
tutti gli inserimenti provocano la riscrittura di tutte le tuple di una gara soprattutto i primi quando
molte tuple non hanno ancora un tempo gara immesso: nel caso peggiore il primo inserimento
provoca una sola scrittura, il secondo 2 il terzo 3 e così via).
Con dato derivato:
Operazione
Inserimento tempi gara
31 accessi in lettura
16.5 accessi in scrittura
Calcolo classifica società
Senza dato Derivato:
Operazione
Inserimento tempi gara
Calcolo classifica società
Concetto
Cartellini
Cartellini
Cartellini
Cartellini
Cartellini
Accessi
1
1
30
15.5
300
Tipo
L
S
L
S
L
Concetto
Cartellini
Cartellini
Cartellini
Accessi
1
1
600
Tipo
L
S
L
Il costo totale con dato derivato è dato dalla somma di
(31+16.5·2)*300
=
19200/manifestazione
300*3
=
900/manifestazione
20100/manifestazione
Commento
Scrittura del tempo gara
Reperimento della classifica atleti
Aggiornamento del dato derivato
Lettura delle posizioni degli atleti.
+
=
senza dato derivato il costo totale sarebbe di (1+1·2)*300+600*3=2700/manifestazione
Si è dunque scelto di non usare i dato derivato. Si noti che nell’ultimo calcolo se avessimo pesato
diversamente le letture per ottenere il piazzamento dalla classifica si sarebbe ottenuto qualcosa
come
(1+1·2)*300+(30L + a*30L)*10 gare *3 < 2700 /manifestazione
in quanto a < 1.
Una soluzione alternativa sarebbe quella di tenere il dato derivato senza però aggiornarlo ad ogni
inserimento di un tempo gara. L’aggiornamento andrebbe eseguito una volta inseriti i tempi di tutti i
partecipanti ad una gara.
Questa soluzione sebbene elegante presenta un problema: come fare a capire se l’utente ha già
immesso tutti i tempi di una gara? Sembrerebbe un problema banale in realtà però non lo è:
L’utente potrebbe comunicarlo esplicitamente all’applicazione che provvederebbe al calcolo dei
piazzamenti (questa era la soluzione proposta dal vecchio GGN). Il committente, però, ha
espressamente richiesto che l’applicazione "disturbi" il meno possibile l’operatore, senza forzarlo
nell’eseguire operazioni di “overhead”.
Questa situazione, unita al fatto che l’utente potrebbe erroneamente comunicare la fine di una gara,
ci ha ulteriormente convinto nello scartare il dato derivato.
12
E/R completo
13
IMPLEMENTAZIONE DELLA BASE DI DATI
Traduzione nel modello relazionale
Dallo schema concettuale discende, secondo le usuali regole di traduzione, il seguente schema
relazionale (dopo ogni tabella viene spiegata la funzione di ogni attributo):
SchemaCategorie(IdSC, Titolo);
AK: Titolo
TITOLO rappresenta
il nome che l’utente assegna a un determinato insieme di categorie
Categoria(IdC, IdSC, Sesso, Nome, AnnoStart, AnnoEnd,)
AK: IdSC,Sesso,Nome
FK: IdSC references SchemaCategorie
La chiave primaria di questa relazione sarebbe dovuta essere IDSC, SESSO, NOME; tuttavia come si
può notare dallo schema E/R molte delle relazioni che seguono hanno un vincolo di foreign key in
cascata a partire da questa relazione: CARTELLINI referenziano GARECONCENTRAMENTO che referenzia
CATEGORIA. Senza l’adozione di una chiave primaria diversa si sarebbe dovuto replicare i tre attributi
in tutte le tabelle citate generando un eccessivo spreco di risorse in quanto l’attributo NOME è una
stringa e CARTELLINIMASCHI e CARTELLINIFEMMINE sono le tabelle più popolate del database. Molto
più semplice e snella la soluzione di adottare IDC come chiave primaria anche alla luce delle query
eseguite sulle tabelle citate.
Esempi di nomi di categorie reali sono: Giovanissimi Maschi
Giovanissimi Femmine
Assoluti Maschi ecc…
ANNOSTART e ANNOEND rappresentano delle età: nel mondo reale invece le categorie vengono
specificate fornendo gli anni di nascita che gli atleti devono avere. La rappresentazione reale ha
ovviamente lo svantaggio di dover essere aggiornata ogni anno e dall’analisi dei requisiti è risultato
dunque più conveniente l’uso di una età minima e massima.
Federazione (SiglaF, NomeF)
NOMEF rappresenta il nome esteso della federazione, SIGLAF solitamente è l’acronimo che identifica
una particolare federazione. Ad es. : NomeF: Federazione Italiana Nuoto
SiglaF: FIN
Societa(CodSoc, NomeS)
NOMES è
il nome esteso di una società sportiva.
FedAmmette (SiglaF, IdSC)
FK: IdSC references SchemaCategorie
FK: SiglaF references Federazione
Atleti (CodAtl, Nome, Cognome, AnnoDiNascita, Sesso, CodSoc)
FK: CodSoc references Societa
14
CODSOC ha due significati all’interno della applicazione: se è nullo significa che l’atleta ha cessato
l’attività agonistica ed è presente nel database a solo scopo di archivio mentre ogni altro valore
rappresenta la società cui l’atleta è correntemente iscritto.
Manifestazioni (CodMan, Denominazione, Sigla, AnnoAgonistico, IdSC, SiglaF, SocOrg)
FK: SocOrg references Societa
FK: IdSC references SchemaCategorie
FK: SiglaF references Federazione
è una stringa che solitamente viene stampata sui cartellini (quelli di cartoncino fisicamente
esistenti) che durante una manifestazione vengono compilati dai cronometristi.
SOCORG rappresenta la società organizzatrice della manifestazione.
Si noti che SIGLAF e IDSC dono chiavi esterne a SCHEMACATEGORIE e FEDERAZIONE, e non
rappresentano un'unica chiave esterna a FEDAMMETTE (come sarebbe stato se si fosse voluto che una
manifestazione potesse usare solo un insieme di categorie ammesso da una certa federazione). Si è
operata questa scelta per supportare le manifestazioni “non ufficiali” impostate secondo i gusti
dell’organizzatore.
ANNOAGONISTICO rappresenta l’anno in cui ha luogo una manifestazione, l’applicazione usa questo
dato insieme alla data di nascita di ogni atleta per calcolare l’età dell’atleta al tempo della
manifestazione al fine di controllare se un certo atleta può essere iscritto o meno a una certa gara
per una specificata categoria.
SIGLA
Concentramenti (CodMan, Nprog, Impianto, Data, Luogo, NCorsie, BaseM, DelayTime)
FK: CodMan references Manifestazioni
è il nome della piscina in cui si svolge il concentramento, la data è vincolata a rispettare
l’anno agonistico specificato nella manifestazione di appartenenza.
Nel campo data il DBMS utilizzato consente di memorizzare anche l’ora di inizio di un
concentramento.
NCORSIE rappresenta il numero di corsie in cui è divisa la vasca e questo è il dato cruciale che
consente la produzione di un programma per il concentramento.
BASEM ha solo lo scopo di archivio e rappresenta la lunghezza della vasca che può essere 25 o 50
metri. Come già ricordato nella sezione Definizioni Varie, nel mondo natatorio i tempi ottenuti a
parità di distanza percorsa dall’atleta in una vasca da 50 metri sono tenuti in diversa considerazione
da quelli ottenuti in vasca corta: in media un atleta che gareggia sui 100m stile libero ottiene tempi
più bassi di 1-2 secondi in vasca corta.
Il DELAYTIME rappresenta il ritardo in minuti con cui le gare del concentramento hanno inizio
sull’orario riportato in DATA. Una delle future funzioni dell’applicazione sarà quella di calcolare
l’ora di partenza di una data batteria partendo da queste informazioni.
IMPIANTO
AtlPartecipa(CodMan, CodAtl, CodSoc)
FK: CodSoc references Societa
FK: CodMan references Manifestazioni
FK: CodAtl references Atleti
In questa relazione si nota l’assenza di CODSOC dalla chiave primaria: questa traduzione dallo
schema E/R non è errata ma esprime il vincolo di unicità della società per cui un atleta gareggia in
una manifestazione (questo vincolo non era stato espresso nello schema E/R).
Questa relazione indica per quale società CODSOC l’atleta CODATL gareggiò nella manifestazione
CODMAN.
15
Discipline(CodGara, Dist, Stile)
AK: Dist,Stile
DIST è
la distanza in metri che una particolare disciplina richiede.
STILE può essere uno degli stili base del nuoto (dorso, rana, delfino, stile libero, misti) ma può
anche assumere valori personalizzati dall’organizzatore di una manifestazione ad es.:
100 Farfalla (stile molto simile e a volte confuso con il delfino)
400 Crawl (altro nome per lo stile libero).
GareConcentramento (CodMan, NProg, IdC, CodGara)
FK: CodMan,NProg references Concentramenti
FK: IdC references Categoria
FK: CodGara references Discipline
In questa relazione ci sono le gare istituite da un particolare concentramento.
CartelliniMaschi (CodMan, NProg, IdC, CodGara, CodAtl, TempoIscriz, TempoGara)
FK: CodMan,NProg,IDC,CodGara references GareConcentramento
FK: CodAtl references Atleti
CartelliniFemmine (CodMan, NProg, IdC, CodGara, CodAtl, TempoIscriz, TempoGara)
FK: CodMan,NProg,IDC,CodGara references GareConcentramento
FK: CodAtl references Atleti
Per quanto riguarda il fatto che queste ultime due tabelle sono identiche si vedano le motivazioni
riportate nella spiegazione dello schema E/R.
TEMPOISCRIZ è il tempo con cui un atleta viene iscritto a una gara, è determinante per stabilire la
corsia di partenza dell’atleta.
TEMPOGARA è la prestazione conseguita dall’atleta.
Questi due campi possono assumere valori particolari come “squalificato”, “ritirato” e “prima
gara” si veda la sezione Definizioni Varie per il significato di questi termini.
PunteggiManif (CodMan, Punti1, Punti2, Punti3, Punti4, Scalare, PuntiMin)
FK: CodMan references Manifestazioni
PunteggiSocieta (CodMan, CodSoc, Punti);
FK: CodMan references Manifestazioni
FK: CodSoc references Societa
Il campo PUNTI esprime il punteggio conseguito da una società in una data manifestazione.
L’aggiornamento della classifica è una operazione costosa e viene lasciata all’utente la libertà di
generarla quando preferisce (ciò significa che non esiste garanzia che il dato qui riportato sia
aggiornato). Il dato viene comunque memorizzato per permettere una rapida consultazione della
classifica una volta generata.
16
IMPLEMENTAZIONE DELLA BASE DI DATI IN SQL
Il DBMS utilizzato per questo progetto è Local Interbase Server versione 4.2 della Borland che
fornisce un supporto quasi completo allo standard SQL92 e mette a disposizione alcune sue
estensioni.
Per assicurare l’univocità delle chiavi primarie numeriche di molte tabelle come ad esempio il
codice per una manifestazione (CODMAN), l’identificatore per la tabella SCHEMACATEGORIE (IDSC) ed
altre tabelle che possono essere esaminate nello script SQL allegato nella appendice A, sono stati
utilizzati dei generatori.
I generatori sono delle funzioni particolari messe a disposizione dal DBMS che, quando chiamate
nel codice SQL ritornano un valore intero assicurandone l’univocità all’interno della tabella.
Istruzioni come
create generator gnIdSC;
set generator gnIdSC to 0;
vengono eseguite nello script SQL di generazione del database (denominato genesi.sql) allo scopo
di creare un generatore e impostare il suo valore corrente a 0 in modo che il primo valore generato
sia 1.
Implementazione di ulteriori vincoli di progetto in SQL
Il tipo di applicazione da generare presenta dei vincoli non esprimibili nello schema E/R e quindi
come semplici vincoli di integrità referenziale o di consistenza. Alcuni dei trigger in seguito
presentati sono stati introdotti per imporre il rispetto di alcuni vincoli altri invece per delegare parte
delle operazioni della applicazione al DBMS ottenendo una semplificazione notevole del codice
dell’applicazione.
Vincolo: validità delle età immesse per una nuova Categoria
Quando si inserisce una nuova categoria all’interno di uno schema bisogna controllare che le età
minima e massima richieste per l’atleta specificate in ANNOSTART e ANNOEND della tabella categoria
non si sovrappongano agli intervalli delle categorie già presenti nel database.
Il trigger che implementa questo controllo è il seguente:
create trigger trControllaEta for Categoria
before insert
as begin
IF (exists (select *
from Categoria
where (sesso=new.Sesso) and
(idsc=new.Idsc) and
((new.ANNOSTART between AnnoStart and AnnoEnd) or
(new.AnnoEND between AnnoStart and AnnoEnd))
))
THEN exception evalCategoria;
end
Il comando exception evalCategoria innalza un’eccezione all’applicazione chiamata
evalCategoria, questa eccezione è definita all’inizio del file script dove è riportato anche il
corrispondente messaggio di errore.
17
Vincolo: impedire modifiche illegali per le tuple della tabella Categoria
L’applicazione permette la modifica delle categorie a patto che si rispettino alcune regole:
• Si può cambiare il sesso di una categoria solo se non vi sono già degli atleti del sesso corrente
iscritti a gare relative a questa categoria. Esempio non è possibile cambiare Giovanissimi
Maschi in Giovanissimi Femmine se esistono degli atleti maschi iscritti a una gara di una
qualunque manifestazione che fa riferimento alla “vecchia” Giovanissimi maschi.
• Si possono modificare le età minima e massima solo se le nuove età non escludono dalla
categoria atleti iscritti a gare che già vi fanno riferimento.
create trigger trValidUpdateCategoria for Categoria
before update
as begin
/*Se si è cambiato il sesso della categoria si controlla che non
ci siano già degli iscritti alle gare che fanno riferimento
alla categoria in esame.*/
IF (new.SESSO<>old.SESSO) THEN
IF (old.Sesso='M') THEN
begin
IF (exists (
select * from CartelliniMaschi
where IDC=new.IDC
)) THEN
exception eupdSesso;
end else
IF (exists (
select * from CartelliniFEMMINE
where IDC=new.IDC
)) THEN
exception eupdSesso;
IF (new.AnnoStart<>old.AnnoStart or new.AnnoEnd<>old.AnnoEnd) THEN
begin
if (new.Sesso='M') THEN
begin
IF (exists (
select *
from CartelliniMaschi C join Categoria CAT on (C.IDC=CAT.IDC) join
Manifestazioni M on (M.IDSC=CAT.IDSC)
join Atleti A on (C.CODATL=A.CODATL)
where IDC=new.IDC and
not ((M.AnnoAgonistico-A.AnnoDiNascita) BETWEEN new.AnnoStart
and new.AnnoEnd)
)) THEN
exception eupdRange;
end else
IF (exists (
select *
from CartelliniFEMMINE C join Categoria CAT on (C.IDC=CAT.IDC) join
Manifestazioni M on (M.IDSC=CAT.IDSC)
join Atleti A on (C.CODATL=A.CODATL)
where IDC=new.IDC and
not ((M.AnnoAgonistico-A.AnnoDiNascita) BETWEEN new.AnnoStart
and new.AnnoEnd)
)) THEN
exception eupdRange;
IF (exists (select *
from Categoria
where (sesso=new.Sesso) and
(idsc=new.Idsc) and
((new.ANNOSTART between AnnoStart and AnnoEnd) or
(new.AnnoEND between AnnoStart and AnnoEnd))
))
THEN exception evalCategoria;
end
18
end !!
Vincolo: eliminazione di tuple inutili
Alle tabelle MANIFESTAZIONI e SCHEMACATEGORIE sono associate le tabelle CONCENTRAMENTI e
con partecipazione obbligatoria (si veda lo schema E/R); l’applicazione consente
l’eliminazione delle tuple sia da concentramenti sia da CATEGORIA, ciò significa che quando in una
delle due tabelle non vi sono più tuple relative a una data manifestazione o ad un dato schema di
categorie anche la tupla di MANIFESTAZIONE o SCHEMACATEGORIA va rimossa per mantenere la
congruenza dei dati nel database.
Per realizzare questo vincolo sono stati scritti i due trigger che seguono dove per ogni
cancellazione su CONCENTRAMENTI o CATEGORIA viene controllato se esistono ancora delle tuple
associate rimuovendo manifestazioni o schemi vuoti.
CATEGORIA
create trigger trCancellaManifestazione for Concentramenti
after delete
as begin
IF (not exists (select * from Concentramenti where CODMAN=old.CODMAN)) THEN
delete from Manifestazioni where CODMAN=old.CodMan;
end !!
create trigger trCancellaSchemi for Categoria
after delete
as begin
IF (not exists (select * from Categoria where IDSC=old.IDSC)) THEN
delete from SchemaCategorie where IDSC=old.IDSC;
end !!
Inserimento e cancellazione automatica dei dati in ATLPartecipa
La tabella ATLPARTECIPA contiene dei dati che derivano dall’iscrizione di atleti a gare della
manifestazione, queste iscrizioni si traducono in un inserimento su CARTELLINIMASCHI o
CARTELLINIFEMMINE a seconda del sesso dell’atleta. Per semplificare il codice nell’applicazione si è
stabilito che quando viene iscritto un atleta in una manifestazione per la prima volta scatta un
trigger che riporta automaticamente in ATLPARTECIPA la società corrente di appartenenza dell’atleta,
al secondo inserimento dello stesso atleta in cartellini non viene effettuato alcun inserimento su
ATLPARTECIPA.
Quando analogamente viene cancellata una iscrizione ad una gara un altro trigger si incarica di
controllare se lo stesso atleta è iscritto ad altre gare nella manifestazione e in caso contrario rimuove
la tupla che lo riguarda anche da ATLPARTECIPA.
Il seguente trigger effettua gli inserimenti quando necessario su ATLPARTECIPA solo per quanto
riguarda i CARTELLINIFEMMINE nel database sono stati implementati anche due trigger analoghi per i
CARTELLINIMASCHI che qui non sono riportati.
create trigger trInsCartelliniF for CartelliniFemmine
after insert
as begin
IF (not exists (select * from AtlPartecipa
where CODMAN=new.CODMAN and
CODATL=new.CODATL)) THEN
insert into AtlPartecipa (CodMan,CodAtl,CodSoc)
values (new.CODMAN, New.CODATL, (select CodSoc
from Atleti
where CodAtl=new.CODATL));
end !!
create trigger trCancAtlPartecipaF for CartelliniFemmine
19
after delete
as begin
IF (not exists (select * from CartelliniFemmine
where CODMAN=old.CODMAN and
CODATL=old.CODATL)) THEN
delete from AtlPartecipa
where CODMAN=old.CodMan and
CODATL=old.CodAtl;
end !!
Modalità TROFEO di una manifestazione
L’applicazione consente due modalità di trattamento per una manifestazione: trofeo e normale. La
differenza tra le due modalità è che un trofeo ha associata una graduatoria delle società i cui
punteggi sono ottenuti in funzione del piazzamento degli atleti nelle singole gare secondo criteri
stabiliti dall’utente.
L’applicazione consente di passare da una modalità all’altra quando si vuole. Nel database una
manifestazione è in modalità trofeo quando esiste una tupla che la riguarda nella tabella
SCHEMAPUNTEGGI in cui vengono memorizzati i criteri per stilare la graduatoria delle società. La
graduatoria potrebbe benissimo essere calcolata ogni volta che l’utente la vuole visualizzare a video
il problema è che stilare la graduatoria è una operazione molto costosa in quanto vanno scandite
tutte le tuple di CARTELLINIMASCHI e CARTELLINIFEMMINE (relative alla manifestazione in esame) e per
ognuna deve essere reperita la posizione per questo motivo si è deciso di memorizzare i punteggi
delle società nella tabella PUNTEGGISOCIETÀ in modo da permettere una rapida consultazione della
classifica. È l’utente che decide quando aggiornare la classifica quindi non c’è garanzia che i dati
memorizzati in PUNTEGGISOCIETÀ siano aggiornati.
I trigger creati nel database si incaricano di inserire le tuple in PUNTEGGISOCIETÀ relative alle
società presenti alla manifestazione ogni volta che viene attivata la modalità trofeo e le rimuovono
se l’utente cambia idea e torna alla modalità normale. Dato che anche una volta attivata la modalità
trofeo l’utente può iscrivere nuove società alla manifestazione bisogna prevedere anche un paio di
trigger che mantengano aggiornata la tabella PUNTEGGISOCIETÀ inserendo o cancellando tuple a
seconda di quante società distinte sono presenti in ATLPARTECIPA.
create trigger trInsSocPunteggi for AtlPartecipa
after insert
as begin
/* il seguente if controlla se è opportuno inserire la tupla punteggio
di una società: si deve inserire solo se l'utente ha abilitato i punteggi
nella manifestazione [if(....)] e la società di cui si vuole creare una
tupla punteggio non ne possiede già una. [ ..and (not exists....]*/
IF ((exists (select *
from PunteggiManif
where CODMAN=new.CODMAN
)) and (not exists (select * from PunteggiSocieta
where CODMAN=new.CODMAN and
CODSOC=new.CODSOC))) THEN
insert into PunteggiSocieta (CodMan,CodSoc)
values (new.CODMAN, New.CODSOC);
end
/* trigger che va in coppia con trInsSocPunteggi
si cancella una tupla punteggi di una società se la tal società non ha più
alcun atleta iscritto alla manifestazione*/
create trigger trCancSocPunteggi for AtlPartecipa
after delete
as begin
IF (not exists (select * from AtlPartecipa
where CODMAN=old.CODMAN and
CODSOC=old.CODSOC)) THEN
delete from PunteggiSocieta
20
where CODMAN=old.CodMan and
CODSOC=old.CodSoc;
end
/*quando viene attivata la modalità trofeo si inseriscono le tuple punteggi
delle società*/
create trigger trCreateSocPunteggi for PunteggiManif
after insert
as begin
insert into PunteggiSocieta (CodMan,CodSoc)
select distinct CodMan,CodSoc
from AtlPartecipa
where CodMan=new.CodMan;
end
create trigger trDestroySocPunteggi for PunteggiManif
after delete
as begin
delete from PunteggiSocieta
where codman=old.codman;
end
Indici
La grande maggioranza delle interrogazioni richiede il nome e il cognome di un atleta con relativa
società di appartenenza: sono stati previsti due indici che ottimizzano gli accessi alle tabelle ATLETI
e SOCIETÀ.
Create Index IDX_NOMIATLETI on Atleti(Cognome, Nome);
Create Index IDX_NOMES on Societa(Nomes);
21
STRUTTURA DELL’APPLICAZIONE
L'applicazione software è stata realizzata con l'ausilio dell'ambiente di sviluppo Delphi 3.0 della
Borland. La realizzazione ha richiesto, oltre al normale sviluppo di una applicazione per ambiente
Windows, lo studio dei metodi messi a disposizione da questo pacchetto per poter interagire con un
DBMS, nel nostro caso Interbase.
La struttura dell’applicazione è riportata nel seguente schema:
Il Borland Database Engine cerca di
mascherare
al
meglio
le
differenze
Applicazione
implementative dei vari DBMS fornendo
un’unica interfaccia all’applicazione. In realtà
quando si utilizza l’SQL bisogna sempre
considerare quali aggiunte o restrizioni allo
Borland Database
standard sono supportate/imposte dallo
Engine
specifico DBMS in uso.
L’applicazione è composta da 65 moduli,
divisi
in due categorie principali: quelli che
Interbase
realizzano l’interfaccia utente e quelli che si
incaricano di mantenere i collegamenti con il
DBMS (detti moduli-dati, o datamodules). I
moduli dati sono organizzati secondo una
specifica
gerarchia.
Il
funzionamento
dell’interfaccia con il DBMS è governato da un
modulo principale (DBMain) cui tutti i moduli
secondari fanno riferimento. Ogni modulo si incarica di realizzare una determinata funzionalità
dell’applicazione. Ad es. creare e modificare manifestazioni, importare manifestazioni, iscrivere
atleti a gare, ecc.
Il DBMain ha tre compiti fondamentali:
§ instaurare la comunicazione con il DBMS;
§ gestire le transazioni;
§ realizzare alcune interrogazioni per reperire dati di uso comune a tutti i moduli
dell’applicazione;
Durante l’avvio dell’applicazione, il DBMain apre una sessione con i parametri specificati
dall’utente in fase di installazione, esegue il login con il DBMS, e se non vengono rilevate delle
eccezioni il controllo passa all’applicazione. Entro certi limiti il modulo si incarica di gestire le
eccezioni più comuni, ad es. informazioni di login errate, database mancante, ecc.
Quando un qualunque modulo secondario deve eseguire una transazione, si invoca il metodo di
una particolare classe presente nel DBMain:
Database.StartTransaction;
quindi vengono eseguite tutte le query interessate dalla transazione, e se tutte le operazioni vanno a
buon fine (non vengono generate eccezioni: violazioni di foreign key, o altri vincoli imposti dalla
struttura del database) viene eseguito il commit della transazione, con una chiamata al
corrispondente metodo:
Database.Commit;
Viceversa, nel caso si verifichino delle eccezioni, viene fatto il rollback.
Database.Rollback;
22
Il linguaggio Delphi è una evoluzione del Pascal fortemente orientato agli oggetti e con ambiente
di sviluppo visuale. Il linguaggio permette di definire delle “proprietà” ossia delle “variabili” di
interfaccia di una classe con le altre. Le virgolette su variabili stanno ad indicare che in realtà non si
tratta di comuni campi dati di un oggetto: al programmatore appaiono come variabili ma in realtà
sono metodi della classe stessa. Questo consente a una classe di rilevare e compiere speciali
operazioni quando il valore di una sua proprietà viene modificato.
Il dialogo con i DBMS utilizzati è realizzato tramite normali “classi” appositamente progettate che
lavorano in sinergia per fornire la massima elasticità alla applicazione.
Al contrario dell’embedded C dove il codice SQL viene inserito direttamente nei sorgenti
dell’applicazione creando non pochi problemi di leggibilità, Delphi mette a disposizione la classe
“TQuery”.
Ogni istanza di TQuery contiene un’unica query per volta, il codice SQL viene immesso in una
apposita proprietà della classe e eseguito quando necessario tramite il metodo Open o ExecSQL. Si
usa il metodo Open per tutte le query che ritornano dati (select) e ExecSQL per tutte le altre.
Il codice SQL immesso può essere statico come ad esempio
Select Nome, Cognome, AnnoDiNascita
From Atleti
Where CODSOC=23;
qui vengono ritornati tutti gli atleti della società 23, il valore discriminante della società è inserito
rigidamente all’interno della stringa contenente la query SQL.
Se quella appena descritta fosse l’unica modalità disponibile di accesso ai dati, scrivere
applicazioni con accesso ai database sarebbe molto scomodo, come per l’embedded C è possibile
definire delle query parametriche ossia query contenenti parametri dipendenti da variabili
dell’applicazione.
Esempio. Si supponga di avere una istanza a TQuery chiamata qryAtleti al cui interno è
memorizzata la stringa SQL seguente:
Select Nome, Cognome, AnnoDiNascita
From Atleti
Where CODSOC=:Codice;
I due punti precedenti un identificatore definiscono un parametro accessibile alla applicazione
tramite istruzioni del tipo:
qryAtleti.Close;
qryAtleti.ParamByName(‘Codice’).AsInteger:=23;
qryAtleti.Open;
//chiude la query
//modifica del parametro
//Esegue la query
Seguendo certe convenzioni in Delphi è possibile fare ritornare alle query anche molto complesse
dei “live results set” ossia insiemi di dati che appena reperiti sono anche immediatamente editabili
dall’utente dando l’impressione di lavorare sulla base dati con la stessa elasticità con cui utilizzando
editor di testo si opera sui file di caratteri.
Per poter rendere modificabile al volo il risultato di una query che può essere ottenuto anche
mediante join di molte tabelle si deve associare a una istanza di Tquery una istanza di un’altra
classe TUpdateSQL che permette di specificare altre tre query parametriche: una per l’inserimento
dei dati, una per la cancellazione e una per la modifica dei dati.
Una volta eseguita la propria query una istanza di TQuery mantiene al proprio interno un cursore
puntante alla tupla corrente all’interno della tabella risultante, tramite metodi come Next, Previous,
First, Last e altri è possibile navigare per tutte le tuple ritornate.
23
I dati della tupla corrente di una istanza di TQuery sono visibili alla applicazione in vari modi, uno
dei più comodi è un array di dati di tipo variabile il cui indice è una stringa contenente il nome del
campo di interesse.
Esempio: sempre facendo riferimento all’istanza qryAtleti si vuole calcolare l’età dell’atleta
corrente nell’anno 1999 e memorizzare il suo nome in una stringa; nel codice dell’applicazione si
procede come segue:
var Età
:integer;
qryAtleti :TQuery;
NomeLungo :string;
begin
…………
…………
Età:=1999-qryAtleti.FieldValues[‘AnnoDiNascita’];
NomeLungo:= qryAtleti.FieldValues[‘Cognome’]+ qryAtleti.FieldValues[‘Nome’];
…………
end;
Ovviamente quanto descritto non ha pretesa di esaurire l’argomento, esistono tutta una serie di
classi che consentono ai dati di essere visualizzati nelle finestre come TDataSource, TDbGrid
TdbEdit ecc. per la cui descrizione si rimanda agli utili quanto voluminosi manuali.
Vediamo dunque come si compone a grandi linee l'applicazione.
24
Descrizione dei principali form
Riportiamo qui di seguito la descrizione di due form significativi, quelli che hanno richiesto un
maggiore impegno di realizzazione.
Form: Gestore singola manifestazione
qryConcentramenti
qryCatGara
qryGareConc
qryCartellini
qryClassifica
Questo form consente all’utente di navigare tra tutte le gare e i concentramenti di cui è composta
una singola manifestazione. Il suo funzionamento si basa su cinque query (per semplicità di
trattazione trascuriamo la divisione tra maschi e femmine) denominate come nei rettangoli in figura.
qryConcentramenti x
qryGareConc x
QryCatGara x
qryCartellini x
QryClassifica x
Tutte le query sono collegate in una gerarchia Master-Slave: quando il cursore indicante la tupla
corrente di una query Master viene spostato vengono rieseguite tutte le query slave:
nello schema che segue ogni query è master per la query alla sua destra e slave per quella alla sua
sinistra.
qryConcentramenti: è una banale query che dato il codice della manifestazione in esame ritorna i
dati relativi a tutti i concentramenti.
qryGareConc: ritorna tutte le discipline presenti nelle gare del concentramento specificato:
select distinct GP.DIST,GP.STILE, GM.CodGara,
(GP.DIST || ' ' || GP.STILE) as gara
from GARECONCENTRAMENTO GM join Discipline GP on (GM.CodGara=GP.CodGara)
where CODMAN=:CODMAN and NPROG=:NPROG;
qryCatGara: data una disciplina in un concentramento ritorna tutte le categorie che hanno una gara
associata alla disciplina specificata
select *
from Categoria C join GARECONCENTRAMENTO G on (C.IDC=G.IDC)
25
where G.CODGARA=:CODGARA and
G.CODMAN=:CODMAN and
G.NPROG=:NPROG;
qryCartellini: ritorna tutti gli iscritti alla gara specificata dalle query precedenti. Il form mette a
disposizione un pulsante tramite il quale è possibile scegliere se visualizzare tutti i partecipanti a
una gara per poterne editare il tempo di gara oppure la classifica provvisoria.
qryClassifica: ritorna la classifica provvisoria (relativa al solo concentramento specificato) della
gara specificata, questa non sarebbe una query complessa il problema è che bisogna comunicare il
numero della posizione e tenere conto di eventuali atleti che in gara ottengono lo stesso tempo.
Esempio: supponiamo che in una certa gara siano iscritti cinque atleti con i seguenti tempi
Atleta
Tempo Posizione
Tizio
1’02
1°
Caio
1’02
1°
Sempronio 1’03
3°
Osvaldo
1’03
3°
Michele
1’05
5°
Come si nota l’atleta immediatamente seguente n atleti parimerito deve avere una posizione in
classifica pari a quanti atleti lo hanno preceduto più uno. Discorso facile a farsi a parole ma non
altrettanto in SQL.
La query che risolve il problema è l’unione di due query: la prima cerca i vincitori (se ci sono dei
parimerito) della gara e gli assegna la posizione 1. La seconda genera la classifica dai secondi in
avanti.
La seconda query opera contando tutti gli alteti che nella stessa gara hanno ottenuto un tempo
migliore del gruppo di parimerito che si sta processando. Si è dovuto usare due query differenti
poiché la seconda query non riporta gli atleti primi classificati in quanto non esiste alcun atleta con
un tempo inferiore al primo. Per contro contare gli atleti che hanno un tempo migliore o uguale non
fornirebbe la giusta posizione in classifica.
/* prima query */
select 1,a.Cognome, a.Nome,S.Nomes, c1.tempogara
from cartellini c1 join Atleti A on (A.CodAtl=c1.CodAtl)
join AtlPartecipa P on (A.CodAtl=P.CodAtl and
P.CodMan=c1.CodMan)
join Societa S on (P.CodSoc=S.CodSoc)
where c1.CodMan=:CodMan and
c1.Nprog=:NProg and
c1.CodGara=:CodGara and
c1.IDC=:IDC and
Tempogara is not null and
Tempogara<=all(select tempogara
from cartelliniMaschi c2
where c2.CodMan=:CodMan and
c2.Nprog=:NProg and
c2.CodGara=:CodGara and
c2.IDC=:IDC and
c2.TempoGara is not null)
union /* seconda query */
select (count(*)+1) as COLUMN1, a.Cognome, a.Nome,S.Nomes, c1.tempogara
from Cartellini c1 join Cartellini c2 on (c1.codMan=c2.codman and
c1.Nprog=c2.Nprog and
c1.Codgara=c2.CodGara and
c1.IDC=c2.IDC)
join Atleti A on (A.CodAtl=c1.CodAtl)
join AtlPartecipa P on (A.CodAtl=P.CodAtl and
P.CodMan=c1.CodMan)
join Societa S on (P.CodSoc=S.CodSoc)
where c1.tempogara>c2.TempoGara and c1.CodMan=:CodMan and
c1.Nprog=:NProg and c1.CodGara=:CodGara and
c1.IDC=:IDC
group by c1.tempogara, a.Cognome, a.Nome,S.Nomes,c1.tempogara;
26
Form: Gestore Iscrizioni Atleti-Gare.
Il seguente form permette l'iscrizione degli atleti alle relative gare. Una volta aperta la
manifestazione, scelto il concentramento ed una gara fra quelle disponibili, si può visualizzare il
form in questione per aggiungere nuovi atleti alla lista degli atleti iscritti.
Una volta aperto il form, si possono eseguire le seguenti operazioni:
• scegliere la società di appartenenza
degli atleti da iscrivere;
• scegliere quale atleta iscrivere in
una lista di atleti compatibili con i
parametri del caso (categoria e tipo
di gara);
• iscrivere
l'atleta/gli
atleti
selezionato/i;
• cancellare
l'iscrizione
di
un'atleta/degli atleti selezionato/i
dalla gara in esame;
Per quanto riguarda l'interazione con
il DBMS, il presente form fa
riferimento
ad
un
datamodule
corrispondente, dtmIscrAtletiGara, con
il quale lavora in stretto contatto. In
questo modulo dati sono raccolte tutte
le query necessarie per visualizzare i dati di interesse del presente form, ed inoltre per realizzare
l'aggiunta e la cancellazione degli atleti alla lista degli iscritti delle varie gare.
Per poter visualizzare la lista delle società
attualmente presenti, si è usata una semplice
query del tipo:
Select CODSOC, NOMES
From SOCIETA
Order By NOMES;
Per visualizzare gli atleti compatibili, invece,
la query necessaria era un po' più complessa:
27
select COGNOME, NOME, ANNODINASCITA, CODATL
from ATLETI a
where ANNODINASCITA >= :pannomin
and ANNODINASCITA <= :pannomax
and SESSO = :psex
and CODSOC = :parsoc
and CODATL not in ( select a.CODATL
from SOCIETA s, CARTELLINIMASCHI c, ATLETI a,
ATLPARTECIPA p
where CODMAN=:pzman
and NPROG=:pzprog
and c.CODGARA=:pzgara
and c.IDC=:pzcat
and p.CODSOC=s.CODSOC
and c.CODATL=a.CODATL
and p.CODMAN=:pzman
and p.CODATL=a.CODATL)
Ed infine, per poter visualizzare gli atleti già iscritti, la query necessaria è la seguente:
select c.CODMAN,c.NPROG,c.IDC,c.CODGARA, a.COGNOME, a.NOME, a.ANNODINASCITA, c.CODATL,
s.NOMES, c.TEMPOISCRIZ
from SOCIETA s, CARTELLINIMASCHI c, ATLETI a, ATLPARTECIPA p
where CODMAN=:CODMAN
and NPROG=:NPROG
and c.CODGARA=:CODGARA
and c.IDC=:IDC
and p.CODSOC=s.CODSOC
and c.CODATL=a.CODATL
and p.CODMAN=:CODMAN
and p.CODATL=a.CODATL;
Una volta selezionati gli atleti che si vogliono aggiungere alla lista degli iscritti, basta premere
l'apposito bottone e gli atleti vengono aggiunti. In seguito tutte le 2 query che visualizzano gli atleti
iscritti e quelli non, vengono opportunamente aggiornate, per confermare l'avvenuta iscrizione.
All'atto
dell'iscrizione,
viene
visualizzato un apposito form nel quale
l'organizzatore può immettere i tempi di
iscrizione degli atleti, ed eventualmente
fare altre scelte, fra le quali decidere se
iscrivere tutti gli atleti selezionati come
partecipanti alla prima gara (per i quali
cioè non è disponibile un tempo di
iscrizione), oppure scegliere se ignorare o
meno l'iscrizione di un atleta a causa
magari di una errata selezione fatta in
partenza.
28
IMPORTAZIONE DI DATI DALLA VECCHIA VERSIONE GGN
Realizzare una funzione che consentisse di importare in maniera automatica i dati della vecchia
applicazione per DOS non è stata una cosa semplice:
il GGN gestisce un’unica manifestazione per volta memorizzando tutti i dati relativi ai tempi, agli
atleti, alle società in un'unica tabella e spargendo gli altri dati come quelli riguardanti le categorie in
una miriade di file cortissimi completamente scorrelati dal database.
La tabella usata nel GGN ha la seguente struttura:
Atleti (
Numero,
Nome,
Anno,
Società,
RCRD,
Gara,
Piazzam,
Codice
- ID numerico dell’atleta
- Cognome e nome dell’atleta
- Anno di nascita
- Stringa con il nome della società (replicata per tutti gli atleti)
- Tempo di iscrizione dell’atleta a una gara
- Tempo ottenuto in gara.
- Posizione ottenuta in classifica.
- Codice numerico che grazie a tabelle interne al codice del GGN
consente di stabilire a quale gara, categoria e sesso si riferisce la tupla
corrente.
);
Nel nostro progetto come si nota dallo schema concettuale per definire una manifestazione occorre
un insieme di dati più grandi: società organizzatrice, federazione di riferimento ecc. Si è quindi
cercato di ricostruire le informazioni mancanti nella maniera più automatica possibile.
Il modulo di importazione comincia col leggere gli anni ammessi per le varie categorie e genera
automaticamente un nuovo schema di categorie compatibile con quello importato. In caso l’utente
voglia utilizzare uno schema di categorie già presente nel database la funzione controlla che lo
schema specificato sia compatibile con quello importato ed esegue il “mapping” tra gli identificativi
di categoria da importare e quelli già presenti nel database.
Una operazione analoga va compiuta con le discipline da importare: i 100 Dorso del GGN
corrisponderanno a CodGara diversi in differenti istanze del database destinatario. Il “mapping” tra
discipline GGN e il nostro CodGara deve dunque avvenire in maniera dinamica e indipendente dal
database correntemente collegato all’applicazione. Se alcune delle discipline importate non esistono
nel nostro database allora l’applicazione si incarica di costruirle.
Stabilire la data di svolgimento di una manifestazione importata non è sempre possibile poiché la
codifica della data nei file dati avviene in un modo non standard di cui non si è ancora riusciti a fare
il reverse engineering. Sfruttando un piccolo trucchetto dovuto alla struttura interna dell’header di
una manifestazione GGN si riesce a importare la data in maniera automatica nel 60% dei tentativi.
In caso non si riesca ad importare la data all’utente viene chiesto di specificarla.
Un altro grosso problema sta nell’importare il minimo numero di atleti possibile: si supponga di
dover importare due manifestazioni aventi gli stessi atleti su di un database scarico: la prima
provocherà l’inserimento di tutte le tuple necessarie dentro la nostra tabella atleti, quando si importa
la seconda bisogna fare attenzione affinché gli atleti già presenti nel database non vengano inseriti
una seconda volta.
Il modulo di importazione considera uguali due atleti se anno: stesso nome/cognome stessa data di
nascita e stessa società di appartenenza. Questo sistema non è perfetto ma non essendovi alcuna
correlazione ne identificativo unitario tra due manifestazioni differenti non è possibile inventare di
meglio.
29
BIBLIOGRAFIA
§ Beneventano, Bergamaschi, Vincini “Progetto relazionale di Basi di Dati” – Pitagora Editrice
Bologna
I seguenti manuali sono editi dalla Borland e forniti come documentazione all’ambiente di sviluppo
Delphi versione “client/server suite”
§ “Delphi Users guide”
§ “Delphi component writers guide”
§ “Delphi Database Application developer guide”
Documentazione relativa al DBMS utilizzato nella applicazione edito da Borland
§ “Interbase Data definition guide”
§ “Interbase language reference”
30
APPENDICE A
(codice di generazione del database)
Qui di seguito riportiamo lo script file utilizzato dall’applicazione per la generazione del database.
/* Errori */
create exception eupdRange "Impossibile variare le età della categoria: problemi con gli
atleti";
create exception eupdSesso "Impossibile variare il SESSO della categoria: problemi con
gli atleti";
create exception eupdAtleti "L'atleta che si vuole modificare è già stato iscritto a
delle gare.";
create exception evalCategoria "L'intervallo di età della categoria si sovrappone a
quelli esistenti";
/* DOMINII */
create domain DSesso as char(1) CHECK
(VALUE="M" or VALUE="F");
create domain DSigla as varchar (10);
create domain DCodice as integer;
create domain DNome as varchar (20);
create domain DAnno as smallint check
(VALUE>=0 and VALUE <9999);
create domain DStile as varchar (40);
create domain DTempo as integer check
((VALUE is null) or (VALUE>=-1));
/* un tempo viene memorizzato in centesimi di secondo,
con 2147483648 centesimi di secondo si possono esprimere gare
della durata massima di 248 giorni filati pari a 8 mesi abbondanti
NOTA: i alcuni valori molto elevati sono valori speciali e indicano
situazioni speciali come PRIMA GARA e SQUALIFICATO
*/
create generator gnIdSC;
set generator gnIdSC to 0;
create generator gnIdC;
set generator gnIdC to 0;
create generator gnCodAtl;
set generator gnCodAtl to 0;
create generator gnCodMan;
set generator gnCodMan to 0;
create generator gnNProg;
set generator gnNprog to 0;
create generator gnCodSoc;
set generator gnCodSoc to 0;
create generator gnCodGara;
set generator gnCodGara to 0;
create table SchemaCategorie
( IdSC
DCodice not null primary key,
Titolo
varchar(40) not null unique
);
create table Categoria
31
( IdSC
DCodice not null,
IdC
DCodice not null,
Nome
varchar(32) not null,
AnnoStart
DAnno, /*eta' dell'atleta a cui si incomincia ad essere di questa
categoria*/
AnnoEnd
DAnno, /*eta' a cui si termina di far parte della categoria */
Sesso
DSesso not null,
primary key (IdC),
foreign key (IdSC) references SchemaCategorie (IdSC),
Unique (IDSC,Nome,Sesso), /*Sarebbe la vera prymary key, si è scelto IDC per
migliorare la gestione dati nella applicazione*/
CHECK (AnnoStart <= AnnoEnd) );
create table Federazione
(
SiglaF
DSigla not null primary key,
NomeF
varchar (32) );
create table Societa
(
CodSoc
NomeS
Unique (NomeS) );
create table
(
SiglaF
IdSC
primary
foreign
foreign
);
DCodice not null primary key,
varchar (32) not null,
FedAmmette
DSigla not null,
DCodice not null,
key (SiglaF,IdSC),
key (IdSC) references SchemaCategorie,
key (SiglaF) references Federazione
create table Atleti
(
CodAtl
Nome
Cognome
AnnoDiNascita
Sesso
CodSoc
/*questo campo puo
null --->
Dcodice not null primary key,
DNome not null,
DNome not null,
Danno not null,
DSesso not null,
DCodice,
essere nullo:
atleta non piu' attivo, non e' iscritto a nessuna societa'
la sua presenza nel database e' a solo scopo di archivio
not null --> societa' a cui l'atleta e' iscritto correntemente */
foreign key (CodSoc) references Societa
);
create table Manifestazioni
(
CodMan
Dcodice not null primary key,
Denominazione
varchar (40),
Sigla
DSigla, /*sigla abbreviata della manifestazione*/
AnnoAgonistico
Danno not null,
IdSC
DCodice not null, /*schema di categorie usato dalla
manifestazione*/
SiglaF
DSigla,
SocOrg
DCodice not null,
foreign key (SocOrg) references Societa,
foreign key (IdSC) references SchemaCategorie,
foreign key (SiglaF) references Federazione
);
create table Concentramenti
(
CodMan
Dcodice not null,
NProg
SmallInt not null,
32
Denominazione
varchar(40),
Data
Date,
Luogo
varchar (40),
NCorsie
SmallInt not null,
BaseM
SmallInt,
DelayTime
SmallInt,
primary key (CodMan,NProg),
foreign key (CodMan) references Manifestazioni
);
create table AtlPartecipa
(
CodMan
DCodice not null,
CodAtl
DCodice not null,
CodSoc
DCodice not null,
primary key (CodMan,CodAtl),
foreign key (CodSoc) references Societa,
foreign key (CodMan) references Manifestazioni,
foreign key (CodAtl) references Atleti
);
create table Discipline
(
CodGara
DCodice not null,
Dist
SmallInt not null,
Stile
DStile not null,
primary key (CodGara),
unique (Dist,Stile)
);
create table
(
CodMan
NProg
IdC
CodGara
primary
foreign
foreign
foreign
);
GARECONCENTRAMENTO
key
key
key
key
DCodice not null,
DCodice not null,
DCodice not null,
DCodice not null,
(CodMan,Nprog,IdC, CodGara),
(CodMan,NProg) references Concentramenti,
(IdC) references Categoria,
(CodGara) references Discipline
/* Le due tabelle che seguono sono le due tabelle più popolate del
database, In realtà sono la stessa tabella e si sarebbe potuto
inglobarle in un unica entità ma per gestire una manifestazione
sportiva in tempo reale in modo efficiente anche da parte di hardware modesto,
si è reso necessaria la suddivisione in due tabelle */
create table CartelliniMaschi
(
CodMan
DCodice not null,
NProg
DCodice not null,
IdC
DCodice not null,
CodGara
DCodice not null,
CodAtl
DCodice not null,
TempoIscriz
DTempo,
TempoGara
DTempo,
primary key (CodMan,Nprog,IdC, CodGara,CodAtl),
foreign key (CodMan,NProg,IDC,CodGara) references GARECONCENTRAMENTO,
foreign key (CodAtl) references Atleti
);
create table CartelliniFemmine
(
CodMan
DCodice not null,
NProg
DCodice not null,
IdC
DCodice not null,
33
CodGara
DCodice not null,
CodAtl
DCodice not null,
TempoIscriz
DTempo,
TempoGara
DTempo,
primary key (CodMan,Nprog,IdC, CodGara,CodAtl),
foreign key (CodMan,NProg,IDC,CodGara) references GARECONCENTRAMENTO,
foreign key (CodAtl) references Atleti
);
create table PunteggiManif
(
CodMan
DCodice not null,
Punti1
smallInt,
Punti2
smallInt,
Punti3
smallInt,
Punti4
smallInt,
Scalare
smallInt,
PuntiMin
smallint,
primary key (CodMan),
foreign key (CodMan) references Manifestazioni
);
create table
(
CodMan
CodSoc
Punti
primary
foreign
foreign
);
PunteggiSocieta
DCodice not null,
DCodice not null,
integer,
key (CodMan, CodSoc),
key (CodMan) references Manifestazioni,
key (CodSoc) references Societa
/* TRIGGERS */
set term !! ;
create trigger trControllaEta for Categoria
before insert
as begin
IF (exists (select *
from Categoria
where (sesso=new.Sesso) and
(idsc=new.Idsc) and
((new.ANNOSTART between AnnoStart and AnnoEnd) or
(new.AnnoEND between AnnoStart and AnnoEnd))
))
THEN exception evalCategoria;
end !!
/* il seguente trigger si incarica mano a mano che vengono iscritti atleti alle
gare di una manifestazione (e quindi indirettamente vengono iscritte delle
società) di inserire una tupla relativa alla società dell'atleta nei punteggi
delle società della manifestazione.
La tupla viene inserita solo se la manifestazione è in modalità trofeo ossia
sono abilitati i punteggi.
Il trigger inserisce solo la tupla non il punteggio della società che verrà
calcolato dall'applicazione quando opportuno.
Questo trigger agisce insieme al suo duale di cancellazione e a
trCreateSocPunteggi che crea le tuple per le società già iscritte la prima
volta che si attivano i punteggi.
*/
create trigger trInsSocPunteggi for AtlPartecipa
after insert
as begin
/* il seguente if controlla se è opportuno inserire la tupla punteggio
di una società: si deve inserire solo se l'utente ha abilitato i punteggi
nella manifestazione [if(....)] e la società di cui si vuole creare una
tupla punteggio non ne possiede già una. [ ..and (not exists....]*/
34
IF ((exists (select *
from PunteggiManif
where CODMAN=new.CODMAN
)) and (not exists (select * from PunteggiSocieta
where CODMAN=new.CODMAN and
CODSOC=new.CODSOC))) THEN
insert into PunteggiSocieta (CodMan,CodSoc)
values (new.CODMAN, New.CODSOC);
end !!
/* trigger che va in coppia con trInsSocPunteggi
si cancella una tupla punteggi di una società se la tal società non ha più
alcun atleta iscritto alla manifestazione*/
create trigger trCancSocPunteggi for AtlPartecipa
after delete
as begin
IF (not exists (select * from AtlPartecipa
where CODMAN=old.CODMAN and
CODSOC=old.CODSOC)) THEN
delete from PunteggiSocieta
where CODMAN=old.CodMan and
CODSOC=old.CodSoc;
end !!
/*quando viene attivata la modalità trofeo si inseriscono le tuple punteggi
delle società*/
create trigger trCreateSocPunteggi for PunteggiManif
after insert
as begin
insert into PunteggiSocieta (CodMan,CodSoc)
select distinct CodMan,CodSoc
from AtlPartecipa
where CodMan=new.CodMan;
end !!
create trigger trDestroySocPunteggi for PunteggiManif
after delete
as begin
delete from PunteggiSocieta
where codman=old.codman;
end !!
/* il seguente trigger impedisce update illegali su categoria*/
create trigger trValidUpdateCategoria for Categoria
before update
as begin
/*Se si è cambiato il sesso della categoria si controlla che non
ci siano già degli iscritti alle gare che fanno riferimento
alla categoria in esame.*/
IF (new.SESSO<>old.SESSO) THEN
IF (old.Sesso='M') THEN
begin
IF (exists (
select * from CartelliniMaschi
where IDC=new.IDC
)) THEN
exception eupdSesso;
end else
IF (exists (
select * from CartelliniFEMMINE
where IDC=new.IDC
)) THEN
exception eupdSesso;
IF (new.AnnoStart<>old.AnnoStart or new.AnnoEnd<>old.AnnoEnd) THEN
begin
if (new.Sesso='M') THEN
begin
IF (exists (
select *
35
from CartelliniMaschi C join Categoria CAT on (C.IDC=CAT.IDC) join
Manifestazioni M on (M.IDSC=CAT.IDSC)
join Atleti A on (C.CODATL=A.CODATL)
where IDC=new.IDC and
not ((M.AnnoAgonistico-A.AnnoDiNascita) BETWEEN new.AnnoStart
and new.AnnoEnd)
)) THEN
exception eupdRange;
end else
IF (exists (
select *
from CartelliniFEMMINE C join Categoria CAT on (C.IDC=CAT.IDC) join
Manifestazioni M on (M.IDSC=CAT.IDSC)
join Atleti A on (C.CODATL=A.CODATL)
where IDC=new.IDC and
not ((M.AnnoAgonistico-A.AnnoDiNascita) BETWEEN new.AnnoStart
and new.AnnoEnd)
)) THEN
exception eupdRange;
IF (exists (select *
from Categoria
where (sesso=new.Sesso) and
(idsc=new.Idsc) and
((new.ANNOSTART between AnnoStart and AnnoEnd) or
(new.AnnoEND between AnnoStart and AnnoEnd))
))
THEN exception evalCategoria;
end
end !!
/* Assicura che gli update su Atleti siano corretti */
create trigger trValidUpdateAtleta for Atleti
before update
as begin
if (new.Sesso<>old.sesso or new.AnnoDiNascita<>old.AnnoDiNascita)
then
if (old.Sesso='M') then
begin
if (exists (select *
from CartelliniMaschi
where CODATL=new.CODATL)) then exception eupdAtleti;
end else
if (exists (select *
from CartelliniFemmine
where CODATL=new.CODATL)) then exception eupdAtleti;
end !!
/*Assicura che quando sono stati eliminati tutti i concentramenti di una manifestazione
anche la manifestazione stessa venga rimossa*/
create trigger trCancellaManifestazione for Concentramenti
after delete
as begin
IF (not exists (select * from Concentramenti where CODMAN=old.CODMAN)) THEN
delete from Manifestazioni where CODMAN=old.CodMan;
end !!
/*Assicura che quando sono stati eliminate tutte le categorie di uno schema
anche lo schema stesso venga rimosso*/
create trigger trCancellaSchemi for Categoria
after delete
as begin
IF (not exists (select * from Categoria where IDSC=old.IDSC)) THEN
delete from SchemaCategorie where IDSC=old.IDSC;
end !!
/*I quattro trigger seguenti si incaricano di mantenere l'allineamento di AtlPartecipa
con gli alteti presenti in CartelliniMaschi o Femmine*/
create trigger trCancAtlPartecipaM for CartelliniMaschi
36
after delete
as begin
IF (not exists (select * from CartelliniMaschi
where CODMAN=old.CODMAN and
CODATL=old.CODATL)) THEN
delete from AtlPartecipa
where CODMAN=old.CodMan and
CODATL=old.CodAtl;
end !!
create trigger trCancAtlPartecipaF for CartelliniFemmine
after delete
as begin
IF (not exists (select * from CartelliniFemmine
where CODMAN=old.CODMAN and
CODATL=old.CODATL)) THEN
delete from AtlPartecipa
where CODMAN=old.CodMan and
CODATL=old.CodAtl;
end !!
create trigger trInsCartelliniF for CartelliniFemmine
after insert
as begin
IF (not exists (select * from AtlPartecipa
where CODMAN=new.CODMAN and
CODATL=new.CODATL)) THEN
insert into AtlPartecipa (CodMan,CodAtl,CodSoc)
values (new.CODMAN, New.CODATL, (select CodSoc
from Atleti
where CodAtl=new.CODATL));
end !!
create trigger trInsCartelliniM for CartelliniMaschi
after insert
as begin
IF (not exists (select * from AtlPartecipa
where CODMAN=new.CODMAN and
CODATL=new.CODATL)) THEN
insert into AtlPartecipa (CodMan,CodAtl,CodSoc)
values (new.CODMAN, New.CODATL, (select CodSoc
from Atleti
where CodAtl=new.CODATL));
end !!
/* TRIGGERS PER LA GENERAZIONE DI CHIAVI PRIMARIE AD USO INTERNO*/
create trigger trGeneraIdSC for SchemaCategorie
before insert
as begin
new.IdSC = GEN_ID (gnIdSC,1);
end !!
create trigger trGeneraIdC for Categoria
before insert
as begin
new.IdC = GEN_ID (gnIdC,1);
end !!
create trigger trGeneraCodAtl for Atleti
before insert
as begin
new.CodAtl = GEN_ID (gnCodAtl,1);
end !!
create trigger trGeneraCodMan for Manifestazioni
before insert
37
as begin
new.CodMan = GEN_ID (gnCodMan,1);
end !!
create trigger trGeneraNprog for Concentramenti
before insert
as begin
new.Nprog = GEN_ID (gnNProg,1);
end !!
create trigger trGeneraCodSoc for Societa
before insert
as begin
new.CodSoc = GEN_ID (gnCodSoc,1);
end !!
create trigger trGeneraCodGara for Discipline
before insert
as begin
new.CodGara = GEN_ID (gnCodGara,1);
end !!
set term ; !!
/* Indici */
CREATE INDEX IDX_NOMIATLETI ON ATLETI(COGNOME, NOME);
CREATE INDEX IDX_NOMES ON SOCIETA(NOMES);
exit;
38
NOTE FINALI
• Per ragioni di Copyright, nella presente stesura non è stato riportato il codice finale
dell'applicazione.
• Questo documento può essere replicato solo integralmente fintantoché rimanga nella sua forma
originale.
• Tutti i marchi citati sono proprietari delle rispettive case produttrici.
39
Scarica