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