GRAND PRIX (GESTIONE DATI DEL MOTOMONDIALE) Introduzione Per la realizzazione di un buon progetto è innanzitutto necessaria una conoscenza approfondita dei requisiti necessari, da la scelta dell’argomento. La scelta del DBMS è ricaduta su SQL Server 7.0, ultima versione prodotta da Microsoft del sistema per la gestione di database relazionali che funziona in background con Windows NT, Windows 95 o 98 . Tale scelta è dettata dall’enorme diffusione del prodotto e dalla facilità di reperire materiale di supporto. Microsoft ha introdotto numerose procedure guidate e strumenti di supporto che permettono sostanzialmente a tutti di accostarsi al prodotto; un’ulteriore spinta alla sua diffusione. Abbiamo utilizzato principalmente due strumenti di SQL Server 7.0: • Enterprise Manager Applicazione che permette di controllare e manipolare quasi tutti gli aspetti della base di dati; • Query Analizer Permette di effettuare una qualsiasi istruzione SQL e di visualizzarne i risultati. L’elemento distintivo e discriminante di un database rimane comunque la progettazione. Specifiche Si vogliono gestire i dati relativi al campionato del mondo di motociclismo nelle classi 125, 250 e 500. Ogni Casa Costruttrice partecipa al campionato fornendo moto ad una o più squadre nelle diverse classi. Le moto fornite sono classificate in base al modello, identificato univocamente da una sigla che permette di risalire alla Casa Costruttrice e dalla classe, e si dividono in ufficiali e clienti: i primi sono più evoluti e competitivi dei secondi. Tutte le moto ricevono durante l’anno gli aggiornamenti proposti dalla Casa Costruttrice anche se con diverse priorità: più alta alle moto dei piloti delle squadre ufficiali ed a quelle di alcuni piloti di squadre semiufficiali (a seconda dei contratti). Per ogni modello si deve inoltre riportare il numero di cilindri, mentre altre caratteristiche tecniche, come ad esempio la cilindrata e il peso, variano in base all’anno in cui un certo modello viene prodotto. Le squadre, identificate dal proprio nome, sono composte da un direttore sportivo, che si occupa di curare i contatti con gli sponsor e di scegliere i piloti, e da un team di meccanici per ogni singolo pilota. Una stessa squadra può partecipare al campionato in un certo anno in più classi contemporaneamente e in questo caso riceve per ogni classe la fornitura di un unico tipo di moto con cui può far correre da uno a tre piloti; le moto fornite a una stessa squadra per le diverse classi devono provenire dalla stessa Casa Costruttrice. Ogni Casa Costruttrice può avere, in un anno, una sola squadra ufficiale per classe, ossia una squadra di cui la Casa Costruttrice stessa è proprietaria. Le squadre il cui proprietario non è il costruttore della moto sono invece dette non ufficiali; per queste è il direttore sportivo ad essere proprietario. Per le squadre che partecipano come ufficiali si ha invece un diverso direttore sportivo per ogni classe al fine di assicurare una gestione differenziata più snella e più efficiente della squadra ufficiale stessa. Una squadra non ufficiale è detta semiufficiale se partecipa al campionato con una moto ufficiale, mentre è detta privata se partecipa con una moto clienti. 1 Nelle squadre ufficiali ho soltanto moto ufficiali dell’anno in corso; mentre una squadra semiufficiale o privata può partecipare ad un campionato con una moto “vecchia”, ossia con anno di produzione precedente a quello in cui corre. Le squadre e i piloti possono avere diversi sponsor che partecipano con una certa quota e una stessa azienda può a sua volta sponsorizzare più squadre o piloti . Il campionato consiste di un certo numero di Gran Premi, ognuno dei quali viene identificato dal nome (Gran Premio d’Italia, Gran Premio di Spagna, …) e può essere disputato una volta sola all’anno mentre in anni diversi lo stesso Gran Premio può essere disputato anche su circuiti diversi. Le informazioni riportate per ogni circuito oltre al nome, che e’ univoco, sono la localita’ e la nazione in cui e’ situato e la lunghezza della pista; su un circuito si puo’ correre al massimo un Gran Premio nello stesso campionato. Un Gran Premio si articola poi nello svolgimento di tre gare, una per ogni classe, ognuna con un numero differente di giri del circuito da effettuare. Per ogni gara occorre memorizzare la classifica dei piloti con i relativi punti conquistati, l’ordine di partenza, il pilota che ha effettuato il giro più veloce e il relativo tempo; nell’ordine di partenza e di arrivo un pilota può occupare una sola posizione ed una posizione può essere occupata da un solo pilota. Oltre a ciò, per ogni gara, si tiene conto dei ritiri riportando: il pilota ritirato, la causa e il giro in cui tale ritiro avviene. Si memorizzano poi le squalifiche e le penalità inflitte ai piloti durante la corsa. E’ interessante tenere inoltre memoria per ogni circuito su cui si e’ disputata almeno una gara del tempo piu’ basso mai realizzato e del pilota che lo ha ottenuto, nonche’ dell’anno in cui e’ stato registrato il record e della casa costruttrice fornitrice della moto. Una parte delicata delle specifiche è rappresentata dal ruolo del proprietario e del direttore sportivo. Al fine di chiarire i vincoli riportiamo di seguito un approfondimento. Il direttore sportivo ha tra le proprie mansioni quella di curare i contatti con gli sponsor della squadra e quella di scegliere i piloti, questo è il motivo per cui nelle squadre più piccole, cioè quelle private, il proprietario è anche il direttore sportivo; è lui infatti ad occuparsi della ricerca del budget e della scelta di chi mettere alla guida delle moto nei suoi team. Al fine di garantire una concorrenza corretta un dirigente può essere proprietario di al massimo una squadra in un anno. Nelle squadre ufficiali si ha un diverso direttore sportivo per ogni categoria il quale, dopo aver selezionato gli sponsor, dovrà confrontarsi con i direttori sportivi della stessa squadra nelle altre classi, in modo che lo sponsor sovvenzioni la squadra ufficiale in tutte le categorie in cui partecipa. Un dirigente che ricopra il ruolo di direttore sportivo di una squadra ufficiale relativamente ad una certa classe non puo’ ricoprire nello stesso anno tale ruolo ne’ in una squadra non ufficiale, ne’ all’interno della stessa squadra ufficiale relativamente alle altre classi. Operazioni op1) Inserimento dei dati relativi a una gara. op2) Visualizzazione delle seguenti classifiche: a)classifica piloti che riporta il nome del pilota e il punteggio totale; b)classifica Case Costruttrici che riporta la casa costruttrice e il punteggio totale; c)classifica parziale che riporta per ogni pilota l’elenco dei circuiti su cui si sono disputate gare con relativo punteggio ottenuto e il punteggio totale. 2 Op3) Albo d‘oro: per ogni classe si visualizza il nome dei piloti vincitori di ameno un titolo mondiale e il numero di titoli vinti nelle relativa classe. Op4) Carriera di un pilota: a)numero gare vinte, b)numero gare effettuate, c)numero giri veloci, d)posizione finale in classifica per ogni anno, e)numero mondiali vinti e in che classe. Op5) Visualizzazione delle caratteristiche di un circuito e relativi record nelle tre classi. L’Op1 può essere eseguita da una figura di utente che si occupa della manutenzione del database; tale utente potrà infatti eseguire inserimenti e cancellazioni controllate da triggers e stored procedures. Le altre sono operazioni di visualizzazione che non comportano modifiche nel DB e sono concesse ad un utente generico. I diritti concessi o negati ai vari tipi di utenti saranno approfonditi più avanti in una sezione dedicata. Volume dei Dati ( 1980 – 2000 ) 9 9 9 9 9 9 9 9 9 9 PILOTI -------------- 400 CIRCUITI ----------- 30 GRAN PREMI ------ 20 MOTO --------------- 300 SQUADRE ---------- 200 TECNICI ------------ 1600 SPONSOR ---------- 2000 GARE ---------------- 900 CLASSIFICHE ----- 900 (ciascuna contenente in media 15 piloti) PART_PIL ---------- 1600 Frequenza delle Operazioni Operazione 1 : 1,25 al mese Operazione 2 : 20 al giorno Operazione 3 : 5 al giorno Operazione 4 : 10 al giorno Operazione 5 : 5 al giorno Progettazione Concettuale 3 La filosofia di progetto che intendiamo seguire è quella di cercare di esprimere il maggior numero possibile di vincoli a partire già dallo schema E/R. Tale scelta ci è suggerita sia dal fatto che non abbiamo vincoli di progettazione per quanto riguarda l’occupazione di memoria e la velocità d’accesso, sia per l’importanza che la fase di progetto in questione riveste nel corso di Base di Dati. Scopo di questo paragrafo è, a partire dalla specifica dei requisiti in linguaggio naturale, la realizzazione dello schema concettuale tramite il modello E/R. SCHEMA E/R COMPLETO 4 SPIEGAZIONE SULLE SCELTE FATTE Segue l’analisi dello schema con alcune considerazioni sulle problematiche incontrate e le scelte fatte. Per una più agevole comprensione riprendiamo lo schema pezzo per pezzo: • MOTO CASA_COSTR PRODUZIONE SIGLA (1,N) (1,1) CLASSE MODELLO N_CILINDRI (T,E) MODUFF MODCLIENTI (1,N) (1,N) (1,1) ANNOP CILIINDRATA MOTOUFF PESO (1,1) ANNOP MOTOCLIENTI CILINDRATA PESO La sigla della moto è un codice che identifica la Casa Costruttrice, può essere lo stesso anche in anni diversi e mi dice se la moto è ufficiale o meno, da questo e dalla classe in cui la moto partecipa posso ottenere anche informazione sul numero dei cilindri e sulla loro disposizione (4 in linea, 2 a V, etc). Una stessa casa può partecipare ad un campionato con moto aventi sigle differenti (anche nella stessa cilindrata). A parità di sigla si hanno generalmente nelle diverse classi diversi numeri di cilindri e a seconda dell’anno di produzione (ANNOP) si possono avere a parità di classe e sigla variazioni di caratteristiche dinamiche della moto quali peso e cilindrata (intesa come cubatura effettiva). Alla gerarchia in figura si applica la traduzione standard. 5 Es: Casa Costruttrice Honda, sigla NSR: sta ad indicare le moto ufficiali della casa giapponese. • SQUADRA (1,1) SU_ IN_CLASSE CLASSE SSU_ IN_CLASSE CLASSE (1,1) SP_ N_CLASSE CLASSE (1,1) DIRSP (1,1) (0,N) (0,N) DIRIGENTE (1,3) (1,3) (1,3) PROPR1 (1,1) CASA_COSTR_TIT SQUFFICIALE (0,N) SQSEMIUFF SQPRIVATA PROPR2 (1,1) NOME_SQ ANNO NOME_SP (T,E) QUOTA PART_SQ SQUADRA (1,N) (1,1) SP_SQ (0,N) TIPO_AZ SPONSOR (0,N) Le squadre partecipano in genere a più edizioni del campionato del mondo e vengono identificate con il loro nome. A seconda dell’anno di partecipazione una squadra può essere ufficiale, semiufficiale o privata. Può avere rapporti di sponsorizzazione con diverse aziende ognuna delle quali partecipa al budget con una certa quota. Una squadra può anche autofinanziarsi, è questo il motivo della cardinalità minima 0. Nelle squadre semiufficiali o private il proprietario, che è anche direttore sportivo, è una figura identificata nel database con la qualifica di dirigente. Nelle squadre ufficiali il proprietario è la Casa Costruttrice che fornisce le moto. Una squadra in uno stesso anno può partecipare in una o più classi ma tutte le moto che fa correre indipendentemente dalla cilindrata devono essere o ufficiali o clienti. Ogni squadra a seconda della classe fa parte delle entità SU, SSU o SP_IN_CLASSE che stanno per SQ.UFF., SQ.SEMIUFF, SQ.PRIVATA. L’associazione tra DIRIGENTE e SU_IN_CLASSE esprime il fatto che nelle squadre ufficiali si ha un direttore sportivo per ogni classe; al contrario per gli altri tipi di squadra l’aver legato dirigente direttamente a SQSEMIUFF e a SQPRIVATA esprime il vincolo che il direttore sportivo sia lo stesso per le diverse classi. 6 L’attributo “casa_costr_tit” di SQUFFICIALE è un dato derivato, un trigger ne garantisce la coerenza; si è reso necessario introdurlo per inserire in Relazionale il vincolo che in un anno una Casa Costruttrice partecipi con non più di una squadra per classe. Per la gerarchia su PART_SQ si utilizza la traduzione standard. CILIINDRATA ANNOP ANNOP CILINDRATA MOTOUFF MOTOCLIENTI PESO PESO (0,N) (0,1) (1,1) CLASSE (1,1) (1,1) CLASSE SU_ IN_CLASSE (0,N) CLASSE SSU_ IN_CLASSE SP_ IN_CLASSE Le squadre UFFICIALI e SEMIUFFICIALI partecipano al campionato con moto ufficiali, mentre quelle PRIVATE corrono con moto non ufficiali (MOTOCLIENTI). Nelle associazioni considerate si evidenzia la fornitura, non l’utilizzo della singola moto: una squadra ha una sola fornitura, ma può ricevere più moto. Si noti che una moto ufficiale può essere utilizzata da una sola squadra ufficiale, che è poi la squadra di proprietà della Casa Costruttrice della moto; inoltre una squadra ufficiale, se partecipa al campionato, usa solo moto prodotte quell’anno stesso (vincolo risolto in Relazionale). Da tali considerazioni deriva la cardinalità (0,1) della partecipazione dell’entità MOTOUFF nell’associazione con SU_IN_CLASSE. Può accadere che una moto non sia utilizzata nell’anno di produzione, ma in anni seguenti da squadre non ufficiali; da qui le cardinalità minime nulle. Il vincolo che una squadra possa partecipare ad un campionato in più classi, ma con moto prodotte tutte della stessa Casa Costruttrice, sarà realizzato con un trigger. 7 • PERSONALE PUNTIP QUOTA CLASSE SP_PIL PART_PIL (1,N) NUMGARA (0,N) ANNO (1,1) 1,1) 1 ANNO PART_IN_TEAM (0,N) RUOLO (1,1) (0,N) DIRIGENTE COGNOME PILOTA NOME TECNICO 0,N) (T,S) TIPO_AZ COGNOME NOME PERSONA SPONSOR NAZIONALITA' LUOGON (1,1) DATAN CODPERS La gerarchia PERSONA è Totale e Sovrapposta perché accade spesso che un pilota con esperienza a fine carriera rimanga nel settore assumendo altri ruoli. Gli attributi “nome” e “cognome” su PILOTA sono dati derivati da PERSONA, quindi bisognerà controllare che i corrispondenti sulle due entità contengano informazioni sempre coerenti (utilizzo di apposito trigger). Abbiamo assunto come chiave alternativa di PILOTA “nome” e “cognome” per rendere più comprensibili i dati presenti nel database. La scelta è permessa dal fatto che i piloti sono personaggi di cui si tiene spesso traccia e che sono ricordati quindi con nomi univoci o soprannomi. ES: KENNY ROBERTS è il nome che hanno in comune due piloti padre e figlio, in questo caso l’omonimia è risolta riferendo il figlio, che corre tuttora, con KENNY JR. Entrambi hanno avuto ottimi risultati e quando si fa riferimento ad uno di loro per ricordare un RECORD o un piazzamento non si hanno confusioni. 8 La chiave di PART_PIL (partecipazione pilota) è rappresentata dalla terna (nome, cognome, anno), ma esiste una chiave alternativa dovuta al fatto che i numeri di gara, scelti dai piloti, devono essere univoci in una categoria ed in un anno. Dallo schema appare evidente l’importanza assunta dal PILOTA nel database, sono infatti ben 9 le associazioni che coinvolgono la sua partecipazione (cfr schema completo). “classe “ è un attributo di PART_PIL derivabile dalle associazioni in cui è coinvolta l’entità, ma è utile riportarlo per poter esprimere alcuni vincoli in Relazionale. Il discorso da fare sullo SPONSOR è del tutto analogo a quello affrontato nel caso delle squadre. Si noti il rilassamento delle cardinalità delle associazioni che coinvolgono le entità PILOTA e TECNICO: vogliamo in questo modo consentire l’inserimento di relativi ad una persona senza l’obbligo di indicare subito il suo ruolo. Se la cardinalità minima fosse unitaria occorrerebbe far rispettare la consistenza. Si noti infine che ogni pilota è seguito da un team di meccanici personale; la coerenza tra gli attributi “anno” nelle due entità (PART_PIL e PART_IN_TEAM) è affrontato nello Schema Relazionale. PART_IN_TEAM indica l’appartenenza di un tecnico ad un team nel corso di un campionato. Per la gerarchia PERSONA si utilizza la traduzione standard. CLASSE (1,3) CLASSE (0,1) PIL_SU SU_ IN_CLASSE ANNO NUMGARA 1 PART_PIL (0,1) CLASSE PIL_SSU SSU_ IN_CLASSE CLASSE (1,3) SP_ IN_CLASSE PUNTIP PRIORITA (0,1) PIL_SP (1,3) Una squadra partecipa ad una categoria con almeno un pilota e al massimo tre. Anche uno solo dei piloti di una squadra semiufficiale può godere della priorità sugli aggiornamenti tecnici a seconda dei contratti, cosa di cui godono invece tutti i piloti delle squadre ufficiali. PIL_SU = partecipazione di un pilota in una squadra ufficiale . PIL_SSU = partecipazione di un pilota in una squadra semiufficiale . PIL_SP = partecipazione di un pilota in una squadra privata . La coerenza tra l’anno di partecipazione di un pilota e quello della squadra in cui corre è realizzata in relazionale. 9 • GP CASA_COSTR COGNOMEPIL NOMEPIL ANNO RECORD TEMPO NOME GP CLASSE (1,1) (1,N) (3,3) LOCALITA (1,1) (0,N) (1,1) (3,3) CIRCUITO LOCAZIONE (1,1) LUNGHEZZA NOMECIRCUITO GIORNO MESE ANNO CLASSE GARA NUMGIRI Nell’entità RECORD sono riportate per ogni classe le informazioni relative al giro piu’ veloce mai realizzato su un certo circuito nel corso di tutte le gare ivi disputate; tale entita` è introdotta per agevolare l’operazione di visualizzazione delle caratteristiche di un circuito e mantiene elementi derivabili dai dati contenuti nelle altre entita’; consideriamo quindi l’entita` RECORD un dato derivato delle cui informazioni viene garantita la coerenza tramite triggers. Ogni GP (Grand Premio) è identificato da un nome univoco, in un anno si può svolgere al più una volta ed in anni diversi può essere organizzato in circuiti diversi: il GP non è strettamente legato al CIRCUITO. Su di un CIRCUITO si può correre al massimo una volta all’anno e nel corso degli anni può essere utilizzato per GP diversi. Si noti infine che nel giorno in cui si organizza un GP, non se ne può svolgere un altro e che in tale occasione si corrono tutte e tre classi (125, 250, 500). 10 Si noti infine la partecipazione opzionale di CIRCUITO nell’associazione con LOCAZIONE: si è scelto così di non appesantire le operazioni di inserimento e cancellazione. In questo modo non occorre affrontare il problema della consistenza, perché è permessa l’esistenza nel database di un CIRCUITO non ancora legato ad un GP. MOTIVOSQ (1,N) PUNTEGGIO SQUALIFICATO (1,1) POS_FIN PUNTI CAUSARIT NON CLASSIFICATO CLASSIFICATO NUMGIRO (T,E) (0,30) (1,1) CLASSE CLASSIFICA GARA (1,1) POS_INIZ (0,N) (1,1) NUMGIRI TIPO (1,N) DESCRIZ CAUSA GVEL IN TEMPO PENALITA NUMGIRO (0,N) (0,N) (0,N) PUNTIP PART_PIL NUMGARA CLASSE ANNO In occasione dello svolgimento di una gara occorre archiviare alcuni dati: ordine di partenza e arrivo, giri veloci, ritiri e penalita’ . L’ entita’ classifica tiene traccia dei piazzamenti di un determinato pilota in una determinata gara. Una posizione (finale o iniziale) puo’ essere occupata da un solo PILOTA, analogamente un pilota puo’ occupare in una determinata gara una sola posizione iniziale o finale. In una gara un pilota puo’ prendere piu’ penalita’ (motivo dell’ impiego di un attributo multiplo e composto sull’ associazione PENALITA’). Per ogni gara (per ognuna delle tre classi di un GP) viene riportato il giro veloce, ossia il tempo piu’ basso realizzato durante la corsa. La chiave alternativa di CLASSIFICATO che coinvolge la posizione finale non e’ modellabile nello schema rappresentato, il vincolo e’ percio’ realizzato solo in relazionale. Si noti inoltre che nell’identificatore di CLASSIFICA e’ presente la chiave di GARA ma il gruppo di attributi che cosi’ ne deriva rappresenta una superchiave per CLASSIFICA; abbiamo quindi provveduto in relazionale a togliere l’attributo ‘classe’ dalla chiave primaria di tale tabella. La coerenza tra l’attributo “anno” di GARA, PENALITA, CLASSIFICA ed il corrispondente di PART_PIL è risolta in Relazionale. Siccome non tutti i partecipanti riescono a concludere la competizione, abbiamo ritenuto opportuno introdurre una gerarchia: ai piloti classificati è possibile assegnare i punti conquistati grazie all’associazione con PUNTEGGIO. Per quanto riguarda i non classificati viene riportata la distinzione fra squalificati e ritirati. 11 Progettazione Logica Semplificazione schema: eliminazione gerarchie ed identificatori esterni, normalizzazione di attributi composti e multipli. ELIMINAZIONE DELLE GERARCHIE 1) MODELLO MODELLO MODUFF (T,E) MODUFF MODCLIENTI MODCLIENTI L’entità di generalizzazione MODELLO è stata tradotta col collasso verso il basso: gerarchia totale ed esclusiva. Si noti che in questo modo si hanno due associazioni con PRODUZIONE anziché una. 2) PART_SQ PART_SQ PART SQ (0,1) (0,1) (1,1) (1,1) 0,1) (T,E) SQUFFICIALE SQSEMIUFF SQPRIVATA SQUFFICIALE 12 SQSEMIUFF 1,1) SQPRIVATA Abbiamo scelto la traduzione standard perché sia le entità padre che figlie partecipano a diverse associazioni e la copertura è totale ed esclusiva. 3) CLASSIFICA CLASSIFICA CLASSIFICA NON CLASSIFICATO CLASSIFICATO NON CLASSIFICATO CLASSIFICATO motivisq SQUALIFICATO motivosq(0,1) Ancora traduzione standard per la gerarchia. Traduzione SUBSET con collasso verso l’alto. 4) PERSONA PERSONA PERSONA (0,1) 0,1) (0,1) (T,E) (1,1) DIRIGENTE PILOTA (1,1) 1,1) TECNICO DIRIGENTE PILOTA TECNICO In questo caso la copertura è totale e sovrapposta, quindi la gerarchia è stata tradotta in modo standard per evitare ripetizioni e ridondanza di dati. Si noti che l’ATTRIBUTO COMPOSTO è stato eliminato e si sono considerati i suoi componenti come attributi semplici. 13 TRASFORMAZIONE DI ATTRIBUTI Oltre all’attributo composto su PERSONA, abbiamo un attributo COMPOSTO e RIPETUTO sulla associazione PENALITA. nordine (1,N) DESCRIZ (1,1) (0,N) (0,N) PENALITA PENALITA (1,1) (0,N) (0,N) Anche se improbabile, un pilota può incorrere più volte nella stessa penalità e in più penalità nello stesso giro. Si rende quindi necessario introdurre un numero d’ordine che identifichi il particolare evento. 14 Traduzione schema E/R in schema relazionale CIRCUITO (nomecircuito , località , lunghezza , siglanazione) RECORD (nomecircuito , classe , cognomepil , nomepil , casa_costr , anno , tempo) FK : nomecircuito REFERENCES CIRCUITO GP (nome) LOCAZIONE (anno , mese , giorno , nomecircuito , nomegp) AK : anno , nomegp AK : anno , mese , giorno FK : nomegp REFERENCES GP FK : nomecircuito REFERENCES CIRCUITO PERSONA (codpers , cognome , nome , datan , luogon , nazionalità) DIRIGENTE (coddirigente) FK : coddirigente REFERENCES PERSONA TECNICO (codtecnico) FK : codtecnico REFERENCES PERSONA PILOTA (copilota , cognome , nome) AK : codpilota FK : codpilota REFERENCES PERSONA OSSERVAZIONE : “nome” e “cognome” sono attributi derivabili dai corrispondenti dell’entità PERSONA. SQUADRA (nome_sq) OSSERVAZIONE : SQUADRA, come GP, sono tabelle di look-up (forniscono una lista valida di valori dalla quale scegliere i dati). PART_SQ (anno , nome_sq) 15 FK : nome_sq REFERENCES SQUADRA PART_PIL (anno , cognomepil , nomepil , numgara , classe , puntip) AK : anno , numgara , classe FK : cognomepil , nomepil REFERENCES PILOTA OSSERVAZIONE : L’attributo “classe” è un dato derivato caricato da apposito trigger. GARA (anno , classe , nomecircuito , numgiri , tempo , cognomepil , nomepil) FK : anno , cognomepil , nomepil REFERENCES PART_PIL FK : anno , nomecircuito REFERENCES LOCAZIONE PENALITA (anno , classe , nomecircuito , numgiro , cognomepil , nomepil , nordine , causa , tipo) FK : anno , cognomepil , nomepil REFERENCES PART_PIL FK : anno , classe , nomecircuito REFERENCES GARA CLASSIFICA (anno , classe , nomecircuito , pos_iniz , cognomepil , nomepil) AK : anno , classe , nomecircuito , pos_iniz FK : anno , cognomepil , nomepil REFERENCES PART_PIL FK : anno , classe , nomecircuito REFERENCES GARA PART_IN_TEAM (anno , cognomepil , nomepil , codtecnico , ruolo) FK : codtecnico REFERENCES TECNICO FK : anno , cognomepil , nomepil REFERENCES PART_PIL OSSERVAZIONE : Nelle ultime quattro tabelle (GARA, PENALITA, CLASSIFICA, PART_IN_TEAM) compare una sola volta “anno” tra gli attributi; abbiamo così aggiunto un vincolo che non era in E/R per avere consistenza tra gli anni in cui un pilota ha corso e quelli in cui si vuol tenere traccia di qualcosa che lo riguarda. PUNTEGGIO (pos_fin , punti) CLASSIFICATO (anno , classe , nomecircuito , pos_fin , cognomepil , nomepil) AK : anno , classe , nomecircuito, pos_fin FK : anno , nomecircuito , cognomepil , nomepil REFERENCES CLASSIFICA FK : pos_fin REFERENCES PUNTEGGIO OSSERVAZIONE : L’AK non era esprimibile nello schema E/R NONCLASSIFICATO (anno , classe , nomecircuito , numgiro , cognomepil , nomepil , causarit , motivosq) FK : anno , nomecircuito , cognomepil , nomepil REFERENCES CLASSIFICA PRODUZIONE (sigla , casa_costr) MODUFF (classe , sigla , n_cilindri) FK : sigla REFERENCES PRODUZIONE 16 MODCLIENTI (classe , sigla , n_cilindri) FK : sigla REFERENCES PRODUZIONE MOTOUFFICIALE (annop , classe , sigla , cilindrata , peso) FK : classe , sigla REFERENCES MODUFF MOTOCLIENTI (annop , classe , sigla , cilindrata , peso) FK : classe , sigla REFERENCES MODCLIENTI SPONSOR (nome_sp , tipo_az) SP_SQ (anno , nome_sq , nome_sp , quota) FK : nome_sp REFERENCES SPONSOR FK : anno , nome_sq REFERENCES PART_SQ SP_PIL (anno , cognomepil , nomepil , nome_sp , quota) FK : nome_sp REFERENCES SPONSOR FK : anno , cognomepil , nomepil REFERENCES PART_PIL SQUFFICIALE (anno , nome_sq , casa_costr_tit) AK : anno , casa_costr_tit FK : anno , nome_sq REFERENCES PART_SQ OSSERVAZIONE : Abbiamo aggiunto un altro vincolo inespresso in E/R, cioè che in un anno una Casa Costruttrice possa partecipare con al più una squadra per classe al campionato: abbiamo introdotto la chiave alternativa (anno, casa_costr_tit). “casa_costr_titolare” è un attributo derivabile (dato derivato) da “casa_costr” nell’entità PRODUZIONE; per mantenere la coerenza è caricato automaticamente da un trigger. SU_IN_CLASSE (anno , classe , nome_sq , sigla ,coddirsp) AK : anno , coddirsp FK : anno , nome_sq REFERENCES SQUFFICIALE FK : anno , classe , sigla REFERENCES MOTOUFFICIALE FK : coddirsp REFERENCES DIRIGENTE SQSEMIUFF (anno , nome_sq , coddirsp) AK: anno , coddirsp FK : anno , nome_sq REFERENCES PART_SQ FK : coddirsp REFERENCES DIRIGENTE SQPRIVATA (anno , nome_sq , coddirsp) AK : anno , coddirsp FK : anno , nome_sq REFERENCES PART_SQ FK : coddirsp REFERENCES DIRIGENTE 17 OSSERVAZIONE :Con le chiavi alternative (anno, coddirsp) nelle relazioni SQSEMIUFF, SQPRIVATA e SU_IN_CLASSE si è garantito che un dirigente non possa essere direttore sportivo di più di una squadra dello stesso tipo nello stesso anno. Per garantire il vincolo più stringente che un dirigente possa essere direttore sportivo al massimo di una squadra in un anno, di qualunque tipo essa sia, dobbiamo inserire un ulteriore controllo (trigger). SSU_IN_CLASSE (anno , classe , nome_sq , sigla , annop) FK : anno , nome_sq REFERENCES SQSEMIUFF FK : annop , classe , sigla REFERENCES MOTOUFFICIALE SP_IN_CLASSE (anno , classe , nome_sq , sigla , annop) FK : anno , nome_sq REFERENCES SQPRIVATA FK : annop , classe , sigla REFERENCES MOTOCLIENTI OSSERVAZIONE : Abbiamo voluto introdurre nello Schema Relazionale il vincolo, inespresso in E/R, che una squadra possa partecipare ad un campionato in una data categoria solo con una moto della stessa classe: in particolar modo non abbiamo ripetuto l’attributo “classe” relativo alla moto nella traduzione delle entità SP_IN_CLASSE, SSU_IN_CLASSE, SU_IN_CLASSE. Allo stesso modo abbiamo introdotto il vincolo che le squadre ufficiali possano partecipare ad un campionato solo con moto prodotte quell’anno stesso: non abbiamo ripetuto l’attributo “annop” relativo a MOTOUFFICIALE nella traduzione delle entità SU_IN_CLASSE. PIL_SP (anno , classe , nome_sq , cognomepil , nomepil) FK : anno , cognomepil , nomepil REFERENCES PART_PIL FK : anno , classe , nome_sq REFERENCES SP_IN_CLASSE PIL_SSU (anno , classe , nome_sq , cognomepil , nomepil , priorità) FK : anno , cognomepil , nomepil REFERENCES PART_PIL FK : anno , classe , nome_sq REFERENCES SSU_IN_CLASSE PIL_SU (anno , classe , nome_sq , cognomepil , nomepil) FK : anno , cognomepil , nomepil REFERENCES PART_PIL FK : anno , classe , nome_sq REFERENCES SU_IN_CLASSE OSSERVAZIONE : Inserendo una volta sola l’attributo “anno” nelle relazioni PIL_SU, PIL_SSU e PIL_SP abbiamo realizzato il vincolo di coerenza tra l’anno di partecipazione della squadra al campionato e l’anno di partecipazione del pilota. Si osservi che non si fa lo stesso discorso con l’attributo “classe”, in quando in PART_PIL esso è un dato derivato. 18 CONCETTO PART_PIL IN CLASSIFICA CLASSIFICATO TIPO VOLUME E R E E 16.000 16.000*15 = 240.000 16.000*15 = 240.000 16.000*10 = 160.000 Dato Derivato 1) puntip Attributo di PART_PIL derivabile da operazioni di conteggio di istanze e da attributi di altre entità OPERAZIONE Op1 Op2 TIPO I I FREQUENZA 20/giorno ≈ 600/mese 15/anno ≈ 1,2/mese Tabella dei Volumi OSS: Si sono considerati valori medi. Volume di 400 piloti che corrono 4 anni ciascuno. Ogni anno un pilota partecipa a 15 gare; di queste ne termina 10, delle quali 6 nei primi 15 classificati (prende almeno un punto). Operazioni Op1 _ Visualizzazione dei punti di un pilota alla fine di un campionato; Op2 _ Inserimento di un pilota classificato con eventuale aggiornamento dei punti per mantenere la consistenza. Tabella delle operazioni 19 Tabella degli accessi • Con dato derivato OSS: Andiamo in PUNTEGGIO ed aggiorniamo il dato derivato solo se il pilota è nei primi 15. • Senza dato derivato Op1 53 accessi in lettura 53*600 = 31800/mese Op1 1 accesso in lettura 1*600 = 600/mese Op2 15/5 accessi in lettura 1+3/5 accessi in scrittura 6.2*1.2 ≈ 7/mese CONCETTO PART_PIL IN CLASSIFICA CLASSIFICATO DEL PUNTEGGIO ACCESSO 1 15 15 10 6 6 TIPO L L L L L L CONCETTO PART_PIL ACCESSO 1 TIPO L CLASSIFICATO DEL PUNTEGGIO CLASSIFICA IN PART_PIL PART_PIL 1 3/5 3/5 3/5 3/5 3/5 3/5 S L L L L L S 20 Op2 1 accesso in scrittura 1*1.2 = 2.4/mese CLASSIFICATO 1 S Conviene tenere il dato derivato. Il risultato è in linea con le aspettative, perché il database realizzato risulta molto “statico”: su di esso si agisce più frequentemente per la consultazione che per variare gli elementi costituenti. 2) RECORD Entità che raccoglie elementi derivabili da attributi di altre entità. Lo studio in questo caso risulta problematico in quanto coinvolge molte entità ed associazioni. D’altra parte il mantenimento del dato derivato appare conveniente per le caratteristiche del database sopraccitate. Per questi motivi affrontiamo uno studio qualitativo con alcune approssimazioni. Tabella dei Volumi CONCETTO CIRCUITO RECORD GARA GVEL TIPO VOLUME E E E R 30 90 900 900 Operazioni Op1 _ Visualizzazione dei dati di un circuito, compresi i record relativi alle tre classi; Op2 _ Inserimento dei tre giri veloci di una gara con eventuale aggiornamento dei record della pista. Tabella delle operazioni OPERAZIONE Op1 Op2 TIPO I I Tabella degli accessi • Con dato derivato 21 FREQUENZA 10/giorno ≈ 300/mese 15/anno ≈ 1,2/mese OSS: Supponiamo che 2 volte su 3 il giro veloce sia anche il record della pista (ciò è dovuto al fatto che i progressi tecnici rendono sempre più veloci le moto). Consideriamo che le maggiori probabilità di segnare il giro veloce siano a favore di piloti di squadre Op1 4 accessi in lettura 4*300 = 1200/mese Op2 10/3 accessi in lettura 10 accessi in scrittura 13.3*1.2 ≈ 16/mese CONCETTO CIRCUITO RECORD ACCESSO 1 3 TIPO L L GARA GVEL PIL_SU PIL_SSU PIL_SP SQ_IN_CLASSE PRODUZIONE RECORD 3 3 3*2/3 1*2/3 1/3*2/3 3*2/3 3*2/3 3*2/3 S S S L L L L S ufficiali e poi semiufficiali; abbiamo così supposto la possibilità di trovare un pilota ufficiale tre volte superiore rispetto ad uno semiufficiale e nove volte rispetto ad uno privato: su nove volte che entriamo in PIL_SU, tre volte dovremo entrare anche in PIL_SSU e tra queste una addirittura in PIL_SP. Una volta trovata la squadra di appartenenza si entra in SU,SSU o SP_IN_CLASSE per risalire alla sigla della moto; con quest’ultima si ottiene la Casa Costruttrice considerando i dati contenuti in PRODUZIONE. • Senza dato derivato Op1 72.3 accessi in lettura 72.3*300 = 21690/mese Op2 6 accessi in scrittura 12*1.2 ≈ 14/mese CONCETTO CIRCUITO LOCAZIONE GARA GVEL PIL_SU PIL_SSU PIL_SP SQ_IN_CLASSE PRODUZIONE GARA GVEL ACCESSO 1 1 30 30 3 1 1/3 3 3 3 3 TIPO L L L L L L L L L S S Il calcolo del costo computazionale dovuto agli accessi contiene grossolane ed inevitabili approssimazioni, ma la differenza tra i risultati risulta macroscopica ed l’esito finale è ancora in linea con le nostre aspettative; si può quindi affermare che conviene ancora il dato derivato. 3) casa_costr_tit 22 Attributo di SU_IN_CLASSE. In questo caso il dato derivato è stato introdotto non già per avere benefici dal punto di vista computazionale, ma per poter esprimere un vincolo che non era realizzabile diversamente. 4) classe Attributo di PART_PIL. Analogamente a prima il dato derivato è stato introdotto per agevolare i controlli necessari. 5) nome , cognome Attributi di PILOTA. Sono stati introdotti per una maggiore leggibilità dei dati contenuti nel DB. 23 Implementazione delle Tabelle TIPI DI DATI DEFINITI DA NOI Oltre ai tipi di dati disponibili è possibile crearne dei nuovi. La creazione consiste in realtà nel configurare opportunamente le proprietà di un tipo di dato standard, in modo da non dover provvedere ogni volta a queste operazioni di configurazione. Vediamo quelli creati da noi: sp_addtype annogp, smallint, ‘not null’ sp_addtype categoria, smallint, ‘not null’ Consideriamo ora alcuni oggetti di database che sono utilizzati nell’implementazione delle tabelle: REGOLE Una regola si collega o associa ad una colonna in una tabella ed indica a SQL server i valori validi per inserire o aggiornare i dati. E’ possibile applicare una regola anche ad un tipo definito dall’utente. Le fasi necessarie alla creazione di una regola in SQL Server 7.0 consistono nel creare la regola per poi associarla a una o più colonne (o a un tipo di dato), onde attivare la regola stessa. Vediamo quelle create da noi: create RULE annorule as @anno >= $1980 create RULE classerule as @classe in (‘125’,’250’,’500’) sp_bindrule ‘annorule’,’annogp’ /*assegnamo le regole ai dati*/ sp_bindrule ’classerule’,’categoria’ Un altro modo per creare una regola consiste nell’usare uno speciale vincolo chiamato CHECK come parte dell’istruzione CREATE TABLE. VALORI PREDEFINITI Un valore predefinito, o default, è un metodo per indicare a SQL Server 7.0 che valore usare per una colonna in una tabella se lo stesso è omesso oppure è nullo. Analogamente al precedente le fasi che portano alla creazione di un valore predefinito consistono nel creare tale valore per poi associarlo a una o più colonne in una tabella per attivarlo. create DEFAULT DEFcrono as ‘9:99:999’ create DEFAULT DEFzero as ‘0’ 24 sp_binddefault ‘DEFcrono’,’GARA.tempo’ /*assegnamo i valori predefiniti ai dati*/ sp_binddefault ‘DEFcrono’,’RECORD.tempo’ sp_binddefault ‘DEFzero’,’PIL_SSU.priorita’ sp_binddefault ‘DEFzero’,’PART_PIL.puntip’ E’ possibile anche specificare un valore predefinito all’atto della creazione di una tabella. REALIZZAZIONE DELLA STRUTTURA DELLA BASE DI DATI Di seguito è riportato il codice SQL di generazione della base di dati. create table CIRCUITO ( nomecircuito varchar (30) not null, localita varchar (30) not null, lunghezza smallint null, siglanazione char (3) not null, constraint primary1 primary key (nomecircuito) ) create table RECORD ( nomecircuito varchar (30) not null, classe categoria not null, cognomepil varchar (30) null, nomepil varchar (30) null, casa_costr varchar (15) null, anno annogp null, tempo char (8) null, constraint primary2 primary key (nomecircuito, classe), constraint fk2 foreign key (nomecircuito) references CIRCUITO ) create table GP ( nome varchar (30) not null, constraint primary3 primary key (nome) ) create table LOCAZIONE ( anno annogp not null, mese varchar (10) not null, check (mese in ('Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre')), giorno tinyint not null, check ((giorno>=1) and (giorno<=31)), 25 nomecircuito varchar (30) not null, nomegp varchar (30) not null, constraint primary4 primary key (anno, nomecircuito), constraint ak41 unique (anno, nomegp), constraint ak42 unique (anno, mese, giorno), constraint fk41 foreign key (nomegp) references GP, constraint fk42 foreign key (nomecircuito) references CIRCUITO ) create table PERSONA ( codpers varchar (20) not null, cognome varchar (30) not null, nome varchar (30) not null, datan smalldatetime null, luogon varchar (30) null, nazionalita char (3) null, constraint primary5 primary key (codpers) ) create table DIRIGENTE ( coddirigente varchar (20) not null, constraint primary6 primary key (coddirigente), constraint fk6 foreign key (coddirigente) references PERSONA ) create table TECNICO ( codtecnico varchar (20) not null, constraint primary7 primary key (codtecnico), constraint fk7 foreign key (codtecnico) references PERSONA ) create table PILOTA ( codpilota varchar (20) not null, cognome varchar (30) not null, nome varchar (30) not null, constraint primary8 primary key (cognome, nome), constraint ak8 unique (codpilota), constraint fk8 foreign key (codpilota) references PERSONA ) create table SQUADRA ( nome_sq varchar (30) not null, constraint primary9 primary key (nome_sq) ) create table PART_SQ ( anno annogp not null, 26 nome_sq varchar (30) not null, constraint primary10 primary key (anno, nome_sq), constraint fk10 foreign key (nome_sq) references SQUADRA ) create table PART_PIL ( anno annogp not null, cognomepil varchar (30) not null, nomepil varchar (30) not null, numgara tinyint null, classe categoria null, puntip smallint null, constraint primary11 primary key (anno, cognomepil, nomepil), constraint ak11 unique (anno, numgara, classe), constraint fk11 foreign key (cognomepil, nomepil) references PILOTA ) create table GARA ( anno annogp not null, classe categoria not null, nomecircuito varchar (30) not null, numgiri tinyint null, tempo char (8) null, cognomepil varchar (30) null, nomepil varchar (30) null, constraint primary12 primary key (anno, classe, nomecircuito), constraint fk121 foreign key (anno, cognomepil, nomepil) references PART_PIL, constraint fk122 foreign key (anno, nomecircuito) references LOCAZIONE ) create table PENALITA ( anno annogp not null, classe categoria not null, nomecircuito varchar (30) not null, numgiro tinyint null, cognomepil varchar (30) not null, nomepil varchar (30) not null, nordine tinyint not null, causa varchar (50) null, tipo varchar (50) null, constraint primary13 primary key (anno, nomecircuito, cognomepil, nomepil, nordine), constraint fk131 foreign key (anno, cognomepil, nomepil) references PART_PIL, constraint fk132 foreign key (anno, classe, nomecircuito) references GARA ) create table CLASSIFICA ( anno annogp not null, 27 classe categoria not null, nomecircuito varchar (30) not null, pos_iniz tinyint not null, check ((pos_iniz>=1)and(pos_iniz<=30)), cognomepil varchar (30) not null, nomepil varchar (30) not null, constraint primary14 primary key (anno, nomecircuito, cognomepil, nomepil), constraint ak14 unique (anno, classe, nomecircuito, pos_iniz), constraint fk141 foreign key (anno, cognomepil, nomepil) references PART_PIL, constraint fk142 foreign key (anno, classe, nomecircuito) references GARA ) create table PART_IN_TEAM ( anno annogp not null, cognomepil varchar (30) not null, nomepil varchar (30) not null, codtecnico varchar (20) not null, ruolo varchar (30) not null, constraint primary15 primary key (anno, codtecnico), constraint fk151 foreign key (codtecnico) references TECNICO, constraint fk152 foreign key (anno, cognomepil, nomepil) references PART_PIL ) create table PUNTEGGIO ( pos_fin tinyint IDENTITY (1,1) not null, check (pos_fin<=30), punti tinyint null, constraint primary16 primary key (pos_fin) ) create table CLASSIFICATO ( anno annogp not null, classe categoria not null, nomecircuito varchar (30) not null, pos_fin tinyint not null, cognomepil varchar (30) not null, nomepil varchar (30) not null, constraint primary17 primary key (anno, nomecircuito, cognomepil, nomepil), constraint ak17 unique (anno, classe, nomecircuito, pos_fin), constraint fk171 foreign key (anno, nomecircuito, cognomepil, nomepil) references CLASSIFICA, constraint fk172 foreign key (pos_fin) references PUNTEGGIO ) create table NONCLASSIFICATO ( anno annogp not null, classe categoria not null, 28 nomecircuito varchar (30) not null, numgiro tinyint null, cognomepil varchar (30) not null, nomepil varchar (30) not null, causarit varchar (30) null, motivosq varchar (50) null, constraint primary18 primary key (anno, nomecircuito, cognomepil, nomepil), constraint fk18 foreign key (anno, nomecircuito, cognomepil, nomepil) references CLASSIFICA ) create table PRODUZIONE ( sigla varchar (5) not null, casa_costr varchar (15) not null, constraint primary19 primary key (sigla) ) create table MODUFF ( classe categoria not null, sigla varchar (5) not null, n_cilindri tinyint not null, check (n_cilindri<=4), constraint primary20 primary key (classe, sigla), constraint fk20 foreign key (sigla) references PRODUZIONE ) create table MODCLIENTI ( classe categoria not null, sigla varchar (5) not null, n_cilindri tinyint not null, check (n_cilindri<=4), constraint primary21 primary key (classe, sigla), constraint fk21 foreign key (sigla) references PRODUZIONE ) create table MOTOUFFICIALE ( annop annogp not null, classe categoria not null, sigla varchar (5) not null, cilindrata smallint null, peso tinyint null, constraint primary22 primary key (annop, classe, sigla), constraint fk22 foreign key (classe, sigla) references MODUFF ) create table MOTOCLIENTI ( annop annogp not null, 29 classe categoria not null, sigla varchar (5) not null, cilindrata smallint null, peso tinyint null, constraint primary23 primary key (annop, classe, sigla), constraint fk23 foreign key (classe, sigla) references MODCLIENTI ) create table SPONSOR ( nome_sp varchar (20) not null, tipo_az varchar (50) null, constraint primary24 primary key (nome_sp) ) create table SP_SQ ( anno annogp not null, nome_sq varchar (30) not null, nome_sp varchar (20) not null, quota int null, constraint primary25 primary key (anno, nome_sq, nome_sp), constraint fk251 foreign key (nome_sp) references SPONSOR, constraint fk252 foreign key (anno, nome_sq) references PART_SQ ) create table SP_PIL ( anno annogp not null, cognomepil varchar (30) not null, nomepil varchar (30) not null, nome_sp varchar (20) not null, quota int null, constraint primary26 primary key (anno, cognomepil, nomepil, nome_sp), constraint fk261 foreign key (nome_sp) references SPONSOR, constraint fk262 foreign key (anno, cognomepil, nomepil) references PART_PIL ) create table SQUFFICIALE ( anno annogp not null, nome_sq varchar (30) not null, casa_costr_tit varchar (15) null, constraint primary27 primary key (anno, nome_sq), constraint ak27 unique (anno, casa_costr_tit), constraint fk27 foreign key (anno, nome_sq) references PART_SQ ) create table SU_IN_CLASSE ( anno annogp not null, classe categoria not null, 30 nome_sq varchar (30) not null, sigla varchar (5) not null, coddirsp varchar (20) not null, constraint primary28 primary key (anno, classe, nome_sq), constraint ak28 unique (anno, coddirsp), constraint fk281 foreign key (anno, nome_sq) references SQUFFICIALE, constraint fk282 foreign key (anno, classe, sigla) references MOTOUFFICIALE, constraint fk283 foreign key (coddirsp) references DIRIGENTE ) create table SQSEMIUFF ( anno annogp not null, nome_sq varchar (30) not null, coddirsp varchar (20) not null, constraint primary29 primary key (anno, nome_sq), constraint ak29 unique (anno, coddirsp), constraint fk291 foreign key (anno, nome_sq) references PART_SQ, constraint fk292 foreign key (coddirsp) references DIRIGENTE ) create table SQPRIVATA ( anno annogp not null, nome_sq varchar (30) not null, coddirsp varchar (20) not null, constraint primary30 primary key (anno, nome_sq), constraint ak30 unique (anno, coddirsp), constraint fk301 foreign key (anno, nome_sq) references PART_SQ, constraint fk302 foreign key (coddirsp) references DIRIGENTE ) create table SSU_IN_CLASSE ( anno annogp not null, classe categoria not null, nome_sq varchar (30) not null, sigla varchar (5) not null, annop annogp not null, constraint primary31 primary key (anno, classe, nome_sq), constraint fk311 foreign key (anno, nome_sq) references SQSEMIUFF, constraint fk312 foreign key (annop, classe, sigla) references MOTOUFFICIALE ) create table SP_IN_CLASSE ( anno annogp not null, classe categoria not null, nome_sq varchar (30) not null, sigla varchar (5) not null, annop annogp not null, 31 constraint primary32 primary key (anno, classe, nome_sq), constraint fk321 foreign key (anno, nome_sq) references SQPRIVATA, constraint fk322 foreign key (annop, classe, sigla) references MOTOCLIENTI ) create table PIL_SP ( anno annogp not null, classe categoria not null, nome_sq varchar (30) not null, cognomepil varchar (30) not null, nomepil varchar (30) not null, constraint primary33 primary key (anno, cognomepil, nomepil), constraint fk331 foreign key (anno, cognomepil, nomepil) references PART_PIL, constraint fk332 foreign key (anno, classe, nome_sq) references SP_IN_CLASSE ) create table PIL_SSU ( anno annogp not null, classe categoria not null, nome_sq varchar (30) not null, cognomepil varchar (30) not null, nomepil varchar (30) not null, priorita bit null, constraint primary34 primary key (anno, cognomepil, nomepil), constraint fk341 foreign key (anno, cognomepil, nomepil) references PART_PIL, constraint fk342 foreign key (anno, classe, nome_sq) references SSU_IN_CLASSE ) create table PIL_SU ( anno annogp not null, classe categoria not null, nome_sq varchar (30) not null, cognomepil varchar (30) not null, nomepil varchar (30) not null, constraint primary35 primary key (anno, cognomepil, nomepil), constraint fk351 foreign key (anno, cognomepil, nomepil) references PART_PIL, constraint fk352 foreign key (anno, classe, nome_sq) references SU_IN_CLASSE ) Triggers Un TRIGGER è un oggetto di database che il DBMS esegue automaticamente quando si inseriscono, aggiornano o cancellano dati in una tabella. Di seguito sono riportati i triggers che si attivano in inserimento. 32 • TRGmoduff I trigger TRGmoduff e TRGmodclienti servono per garantire l'esclusività nella copertura della gerarchia MOTO. if exists ( select name from sysobjects where name = 'TRGmoduff' and type = 'tr' ) drop trigger TRGmoduff go create trigger TRGmoduff on MODUFF for insert as declare @sigla varchar (5) declare @classe categoria select @sigla = sigla, @classe = classe from INSERTED if exists ( select * from INSERTED I, MODCLIENTI MC where I.sigla = MC.sigla and I.classe = MC.classe ) begin RAISERROR ('Esiste già un modello con sigla %s , classe %d, di tipo MODCLIENTI.',16,1, @sigla, @classe) ROLLBACK TRANSACTION end go • TRGmodclienti if exists ( select name from sysobjects where name = 'TRGmodclienti' and type = 'tr' ) drop trigger TRGmodclienti go create trigger TRGmodclienti on MODCLIENTI for insert 33 as declare @sigla varchar (5) declare @classe categoria select @sigla = sigla, @classe = classe from INSERTED if exists ( select * from INSERTED I, MODUFF MU where I.sigla = MU.sigla and I.classe = MU.classe ) begin RAISERROR ('Esiste già un modello con sigla %s , classe %d, di tipo MODUFF.',16,1, @sigla, @classe) ROLLBACK TRANSACTION end go • TRGaggiornapunti Il trigger TRGaggiornapunti si occupa di aggiornare il dato derivato ‘puntip’ nella tabella PART_PIL all’atto dell’inserimento nella tabella CLASSIFICATO: vengono aggiunti i punti corrispondenti alla posizione finale conquistata dal pilota nella gara considerata. Si noti che solo i primi 15 classificati conquistano punti. if exists ( select name from sysobjects where name = 'TRGaggiornapunti' and type = 'tr' ) drop trigger TRGaggiornapunti go create trigger TRGaggiornapunti on CLASSIFICATO for insert as declare @puntiA tinyint declare @puntiB smallint declare @puntiC smallint declare @anno annogp declare @posizione tinyint declare @cognome varchar(30) declare @nome varchar(30) select @anno=anno, @posizione=pos_fin, @cognome=cognomepil, @nome=nomepil from INSERTED select @puntiA=punti from PUNTEGGIO 34 where pos_fin=@posizione if (@posizione <= 15) begin select @puntiB=puntip from PART_PIL where anno=@anno and cognomepil=@cognome and nomepil=@nome set @puntiC=@puntiA+@puntiB update PART_PIL set puntip=@puntiC where anno=@anno and cognomepil=@cognome and nomepil=@nome end go /*casting automatico*/ • TRGrecord Trigger che si occupa di aggiornare il dato derivato record di un circuito nella tabella RECORD all’atto dell’inserimento del giro veloce nella tabella GARA nel caso il tempo inserito sia più basso di quello precedentemente conseguito. Si noti che è stato assegnato un valore di default per il campo tempo sia in GARA che in RECORD. if exists ( select name from sysobjects where name = 'TRGrecord' and type = 'tr' ) drop trigger TRGrecord go create trigger TRGrecord on GARA for insert, update as declare @anno annogp declare @classe categoria declare @circuito varchar(30) declare @nuovotempo char(8) declare @cognomepil varchar(30) declare @nomepil varchar(30) declare @casacostr varchar(15) select @anno=anno, @classe=classe, @circuito=nomecircuito, @nuovotempo=tempo, @cognomepil=cognomepil, @nomepil=nomepil from INSERTED if exists ( select * 35 from RECORD R, INSERTED I where I.classe = R.classe and I.nomecircuito = R.nomecircuito and I.tempo < R.tempo ) /*Occorre recuperare il dato relativo alla Casa Costruttrice della moto con cui il pilota ha segnato il record della pista*/ begin select @casacostr = PR.casa_costr from INSERTED I, PIL_SP PSP, SP_IN_CLASSE SPIC, PRODUZIONE PR where I.anno = PSP.anno and I.cognomepil = PSP.cognomepil and I.nomepil = PSP.nomepil and PSP.anno = SPIC.anno and PSP.classe = SPIC.classe and PSP.nome_sq = SPIC.nome_sq and SPIC.sigla = PR.sigla select @casacostr = PR.casa_costr from INSERTED I, PIL_SSU PSS, SSU_IN_CLASSE SSIC, PRODUZIONE PR where I.nomepil = PSS.nomepil and I.cognomepil = PSS.cognomepil and I.anno = PSS.anno and PSS.anno = SSIC.anno and PSS.classe = SSIC.classe and PSS.nome_sq = SSIC.nome_sq and SSIC.sigla = PR.sigla select @casacostr = PR.casa_costr from INSERTED I, PIL_SU PSU, SU_IN_CLASSE SUIC, PRODUZIONE PR where I.nomepil = PSU.nomepil and I.cognomepil = PSU.cognomepil and I.anno = PSU.anno and PSU.anno = SUIC.anno and PSU.classe = SUIC.classe and PSU.nome_sq = SUIC.nome_sq and SUIC.sigla = PR.sigla /*solo una delle tre select da un risultato e fa l’assegnamento*/ update RECORD set cognomepil=@cognomepil, nomepil=@nomepil, casa_costr=@casacostr, anno=@anno, tempo=@nuovotempo where nomecircuito=@circuito and classe=@classe end go 36 • TRGclassec Questo trigger verifica la coerenza tra la classe di un campionato a cui partecipa un pilota e quella della CLASSIFICA in cui voglio inserire un risultato relativamente a quel pilota (es: evito che un pilota che corre nella classe 250 compaia nella classifica di una gara della classe 125) if exists ( select name from sysobjects where name = 'TRGclassec' and type = 'tr' ) drop trigger TRGclassec go create trigger TRGclassec on CLASSIFICA for insert as declare @anno annogp declare @classe categoria declare @cognomepil varchar (30) declare @nomepil varchar(30) select @anno=anno, @classe=classe, @cognomepil=cognomepil, @nomepil=nomepil from INSERTED if not exists ( select * from PART_PIL where nomepil=@nomepil and cognomepil=@cognomepil and anno=@anno and classe=@classe ) begin RAISERROR ('Errore: il pilota %s %s non risulta correre nella classe %d nell’anno %d, quindi non può essere inserito nella classifica relativa a tale classe.',16, 1, @nomepil, @cognomepil, @classe, @anno) ROLLBACK TRANSACTION end go • TRGclasseg Questo trigger verifica la coerenza tra la classe di un campionato a cui partecipa un pilota e quella della GARA in cui voglio inserire un giro veloce (es: evito che un pilota che corre nella classe 250 risulti aver compiuto un giro veloce in una gara della classe 125). Siccome l’inserimento di un giro veloce puó 37 avvenire anche tramite procedura con un update su GARA, il trigger vale sia per l’inserimento che per l’aggiornamento. if exists ( select name from sysobjects where name = 'TRGclasseg' and type = 'tr' ) drop trigger TRGclasseg go create trigger TRGclasseg on GARA for insert, update /*insert per l’inserimento diretto da parte del dbo, update per la procedura di inserimento*/ as declare @anno annogp declare @classe categoria declare @cognomepil varchar (30) declare @nomepil varchar (30) select @anno=anno, @classe=classe, @cognomepil=cognomepil, @nomepil=nomepil from INSERTED if ((@cognomepil<>null) and (@nomepil<>null)) if not exists ( select * from PART_PIL where nomepil=@nomepil and cognomepil=@cognomepil and anno=@anno and classe=@classe ) begin RAISERROR ('Errore: il pilota %s %s non risulta correre nella classe %d nell’anno %d, quindi non può effettuare il giro veloce di una gara relativa a tale classe.',16, 1, @nomepil, @cognomepil, @classe, @anno) ROLLBACK TRANSACTION end go • TRGclassep Questo trigger verifica la coerenza tra la classe di un campionato a cui partecipa un pilota e quella della gara in cui voglio inserire delle PENALITA’ relative a quel pilota (es: evito che un pilota che corre nella classe 250 subisca una penalità in una gara della classe 125). if exists ( select name from sysobjects where name = 'TRGclassep' 38 and type = 'tr' ) drop trigger TRGclassep go create trigger TRGclassep on PENALITA for insert as declare @anno annogp declare @classe categoria declare @cognomepil varchar (30) declare @nomepil varchar (30) select @anno=anno, @classe=classe, @cognomepil=cognomepil, @nomepil=nomepil from INSERTED if not exists ( select * from PART_PIL where nomepil=@nomepil and cognomepil=@cognomepil and anno=@anno and classe=@classe ) begin RAISERROR ('Errore: il pilota %s %s non risulta correre nella classe %d nell’anno %d, quindi non può essere inserito in penalità relativamente a tale classe.',16, 1, @nomepil, @cognomepil, @classe, @anno) ROLLBACK TRANSACTION end go • TRGlocazione Trigger che si occupa di verificare la validità dei dati giorno e mese per i mesi con un numero di giorni inferiore a 31; per gli altri la validità é garantita dal vincolo di colonna su giorno nella tabella LOCAZIONE. if exists ( select name from sysobjects where name = 'TRGlocazione' and type = 'tr' ) drop trigger TRGlocazione go create trigger TRGlocazione on LOCAZIONE for insert as 39 declare @giorno tinyint declare @mese varchar (10) select @giorno=giorno, @mese=mese from INSERTED if (@mese = 'Febbraio' and @giorno > 29) begin RAISERROR ('ERRORE! Febbraio ha 29 giorni. ', 16, 1) ROLLBACK TRANSACTION end else if (@mese = 'Aprile' and @giorno = 31) begin RAISERROR ('ERRORE! Aprile ha 30 giorni. ', 16, 1) ROLLBACK TRANSACTION end else if (@mese = 'Giugno' and @giorno = 31) begin RAISERROR ('ERRORE! Giugno ha 30 giorni. ', 16, 1) ROLLBACK TRANSACTION end else if (@mese = 'Settembre' and @giorno = 31) begin RAISERROR ('ERRORE! Settembre ha 30 giorni. ', 16, 1) ROLLBACK TRANSACTION end else if (@mese = 'Novembre' and @giorno = 31) begin RAISERROR ('ERRORE! Novembre ha 30 giorni. ', 16, 1) ROLLBACK TRANSACTION end go • TRGsp_in_classe Questo trigger controlla che una squadra privata partecipi al campionato in un certo anno nelle diverse classi con moto fornite dalla stessa Casa Costruttrice. if exists ( select name from sysobjects where name = 'TRGsp_in_classe' and type = 'tr' ) drop trigger TRGsp_in_classe 40 go create trigger TRGsp_in_classe on SP_IN_CLASSE for insert as declare @anno annogp declare @classe categoria declare @nomesq varchar (30) declare @sigla varchar (5) declare @casacostr varchar (15) begin select @anno=anno, @classe=classe, @nomesq=nome_sq, @sigla=sigla from INSERTED select @casacostr = casa_costr from PRODUZIONE where sigla = @sigla if exists ( select * from SP_IN_CLASSE SIC, PRODUZIONE P where SIC.anno = @anno and SIC.nome_sq = @nomesq and SIC.sigla = P.sigla and P.casa_costr <> @casacostr ) begin RAISERROR ('Le squadre con nome %s non possono partecipare nell`anno %d al campionato con moto di case costruttrici differenti, errore.', 16, 1, @nomesq, @anno) ROLLBACK TRANSACTION end end go • TRGssu_in_classe Questo trigger controlla che una squadra semiufficiale partecipi al campionato nelle diverse classi con moto fornite dalla stessa Casa Costruttrice. if exists ( select name from sysobjects where name = 'TRGssu_in_classe' and type = 'tr' ) drop trigger TRGssu_in_classe go create trigger TRGssu_in_classe on SSU_IN_CLASSE for insert as 41 declare @anno annogp declare @classe categoria declare @nomesq varchar (30) declare @sigla varchar (5) declare @casacostr varchar (15) select @anno=anno, @classe=classe, @nomesq=nome_sq, @sigla=sigla from INSERTED select @casacostr = casa_costr from PRODUZIONE where sigla = @sigla if exists ( select * from SSU_IN_CLASSE SSIC, PRODUZIONE P where SSIC.anno = @anno and SSIC.nome_sq = @nomesq and SSIC.sigla = P.sigla and P.casa_costr <> @casacostr ) begin RAISERROR ('Le squadre con nome %s non possono partecipare nell`anno %d al campionato con moto di case costruttrici differenti, errore.', 16, 1, @nomesq, @anno) ROLLBACK TRANSACTION end go • TRGsu_in_classe Questo trigger controlla che: 1)il direttore sportivo di una squadra ufficiale non possa essere direttore sportivo, nel medesimo anno, di squadre di diverso tipo. Il fatto che non possa essere direttore sportivo di un’altra squadra ufficiale e’ garantito dalla chiave alternativa nella tabella SU_IN_CLASSE. 2)una squadra ufficiale partecipi al campionato nelle diverse classi in un certo anno con moto fornite dalla stessa casa costruttrice. Inoltre si occupa di settare il dato derivato casa_costr_tit nella relazione SQUFFICIALE nel caso nella relazione SU_IN_CLASSE non ci sia ancora nessuna tupla con i valori degli attributi nome_sq e anno uguali a quelli inseriti. if exists ( select name from sysobjects where name = 'TRGsu_in_classe' and type = 'tr' ) drop trigger TRGsu_in_classe go create trigger TRGsu_in_classe on SU_IN_CLASSE for insert 42 as declare @anno annogp declare @classe categoria declare @nomesq varchar (30) declare @sigla varchar (5) declare @coddirsp varchar (20) declare @casacostr varchar (15) declare @cognomepropr varchar (30) declare @nomepropr varchar (30) select @anno=anno, @classe=classe, @nomesq=nome_sq, @sigla=sigla, @coddirsp=coddirsp from INSERTED select @casacostr = casa_costr from PRODUZIONE where sigla = @sigla if not exists ( select * from SU_IN_CLASSE where nome_sq = @nomesq and anno = @anno and classe <> @classe ) update SQUFFICIALE set casa_costr_tit = @casacostr where nome_sq = @nomesq and anno = @anno if exists ( select * from SQSEMIUFF where coddirsp = @coddirsp and anno = @anno ) begin select @cognomepropr = cognome, @nomepropr = nome from PERSONA where codpers = @coddirsp RAISERROR ('Il dirigente %s %s possiede già una squadra semiufficiale nell`anno %d, errore.', 16, 1,@cognomepropr,@nomepropr,@anno) ROLLBACK TRANSACTION end if exists ( select * from SQPRIVATA where coddirsp = @coddirsp and anno = @anno ) begin select @cognomepropr = cognome, @nomepropr = nome from PERSONA where codpers = @coddirsp 43 RAISERROR ('Il dirigente %s %s possiede già una squadra privata nell`anno %d, errore.', 16, 1, @cognomepropr, @nomepropr, @anno) ROLLBACK TRANSACTION end if exists ( select * from SU_IN_CLASSE SUIC, PRODUZIONE PR where SUIC.anno = @anno and SUIC.nome_sq = @nomesq and SUIC.sigla = PR.sigla and PR.casa_costr <> @casacostr ) begin RAISERROR ('Le squadre con nome %s non possono partecipare nell`anno %d al campionato con moto di case costruttrici differenti, errore.', 16, 1, @nomesq, @anno) ROLLBACK TRANSACTION end go • TRGsqsemiuff Questo trigger controlla che il direttore sportivo di una squadra semiufficiale non possa essere direttore sportivo, nel medesimo anno, di squadre di diverso tipo. Il fatto che non possa essere direttore sportivo di un’altra squadra semiufficiale e’ garantito dalla chiave alternativa nella tabella SQSEMIUFF. if exists ( select name from sysobjects where name = 'TRGsqsemiuff' and type = 'tr' ) drop trigger TRGsqsemiuff go create trigger TRGsqsemiuff on SQSEMIUFF for insert as declare @anno annogp declare @codice varchar (20) declare @cognome varchar (30) declare @nome varchar (30) select @anno=anno, @codice=coddirsp from INSERTED if exists ( select * from SQPRIVATA where coddirsp = @codice and anno = @anno 44 ) begin select @cognome = cognome, @nome = nome from PERSONA where codpers = @codice RAISERROR ('Il proprietario %s %s possiede già una squadra privata nell`anno %d, errore.', 16, 1, @cognome, @nome, @anno) ROLLBACK TRANSACTION end if exists ( select * from SU_IN_CLASSE where coddirsp = @codice and anno = @anno ) begin select @cognome = cognome, @nome = nome from PERSONA where codpers = @codice RAISERROR ('Il proprietario %s %s è già direttore sportivo di una squadra ufficiale nell`anno %d, errore.', 16, 1, @cognome, @nome, @anno) ROLLBACK TRANSACTION end go • TRGsqprivata Questo trigger controlla che il direttore sportivo di una squadra privata non possa essere direttore sportivo, nel medesimo anno, di squadre di diverso tipo. Il fatto che non possa essere direttore sportivo di un’altra squadra privata è garantito dalla chiave alternativa nella tabella SQPRIVATA. if exists ( select name from sysobjects where name = 'TRGsqprivata' and type = 'tr' ) drop trigger TRGsqprivata go create trigger TRGsqprivata on SQPRIVATA for insert as declare @anno annogp declare @codice varchar (20) declare @cognome varchar (30) declare @nome varchar (30) 45 select @anno=anno, @codice=coddirsp from INSERTED if exists ( select * from SQSEMIUFF where coddirsp = @codice and anno = @anno ) begin select @cognome = cognome, @nome = nome from PERSONA where codpers = @codice RAISERROR ('Il proprietario %s %s possiede già una squadra privata nell`anno %d, errore.', 16, 1, @cognome, @nome, @anno) ROLLBACK TRANSACTION end if exists ( select * from SU_IN_CLASSE where coddirsp = @codice and anno = @anno ) begin select @cognome = cognome, @nome = nome from PERSONA where codpers = @codice RAISERROR ('Il proprietario %s %s è già direttore sportivo di una squadra ufficiale nell`anno %d, errore.', 16, 1, @cognome, @nome, @anno) ROLLBACK TRANSACTION end go • TRGpil_sp Questo trigger controlla che: 1)un pilota inserito in una squadra privata un certo anno, non sia già presente in squadre di tipo diverso nello stesso anno. 2)una squadra privata un certo anno non possa correre con più di tre piloti. Inoltre si occupa di settare il valore del dato derivato classe nella tabella PART_PIL lasciato a NULL fino all`inserimento di una tupla nella tabella PIL_SP. if exists ( select name from sysobjects where name = 'TRGpil_sp' and type = 'tr' ) drop trigger TRGpil_sp go 46 create trigger TRGpil_sp on PIL_SP for insert as declare @nome varchar (30) declare @cognome varchar (30) declare @anno annogp declare @nomesq varchar (30) declare @classe categoria select @nome=nomepil, @cognome=cognomepil, @anno=anno, @nomesq=nome_sq, @classe=classe from INSERTED if exists ( select * from PIL_SU PSU, INSERTED I where PSU.anno = I.anno and PSU.nomepil = I.nomepil and PSU.cognomepil = I.cognomepil ) begin RAISERROR ('Il pilota %s %s corre già in una squadra ufficiale di nome %s ', 16, 1, @nome, @cognome, @nomesq) ROLLBACK TRANSACTION end if exists ( select * from PIL_SSU PSS, INSERTED I where PSS.anno = I.anno and PSS.nomepil = I.nomepil and PSS.cognomepil = I.cognomepil ) begin RAISERROR ('Il pilota %s %s corre già in una squadra semiufficiale di nome %s ', 16, 1, @nome, @cognome, @nomesq) ROLLBACK TRANSACTION end if 3 < ( select count (*) from INSERTED I, PIL_SP PSP where I.classe = PSP.classe and I.anno = PSP.anno and I.nome_sq = PSP.nome_sq ) begin RAISERROR ('La squadra %s non può partecipare con più di tre piloti nell’anno %d e nella classe %d. ', 16, 1, @nomesq, @anno, @classe) ROLLBACK TRANSACTION end update PART_PIL set classe=@classe 47 where nomepil=@nome and cognomepil=@cognome and anno=@anno go • TRGpil_ssu Questo trigger controlla che: 1)che un pilota inserito in una squadra semiufficiale in un certo anno non sia già presente in squadre di tipo diverso nello stesso anno; 2)che una squadra semiufficiale in un certo anno non possa correre con più di tre piloti. Inoltre si occupa di settare il valore del dato derivato classe nella tabella PART_PIL lasciato a NULL fino all`inserimento di una tupla nella tabella PIL_SSU. if exists ( select name from sysobjects where name = 'TRGpil_ssu' and type = 'tr' ) drop trigger TRGpil_ssu go create trigger TRGpil_ssu on PIL_SSU for insert as declare @nome varchar (15) declare @cognome varchar (15) declare @nomesq varchar (15) declare @classe categoria declare @anno annogp select @nome = nomepil, @cognome = cognomepil, @nomesq = nome_sq, @anno=anno, @classe=classe from INSERTED if exists ( select * from PIL_SP PSP, INSERTED I where PSP.anno = I.anno and PSP.nomepil = I.nomepil and PSP.cognomepil = I.cognomepil ) begin RAISERROR ('Il pilota %s %s corre già in una squadra privata di nome %s ', 16, 1, @nome, @cognome, @nomesq) ROLLBACK TRANSACTION end if exists ( select * from PIL_SU PSU, INSERTED I 48 where PSU.anno = I.anno and PSU.nomepil = I.nomepil and PSU.cognomepil = I.cognomepil ) begin RAISERROR ('il pilota %s %s corre già in una squadra ufficiale di nome %s ', 16, 1, @nome, @cognome, @nomesq) ROLLBACK TRANSACTION end if 3 < ( select count (*) from INSERTED I, PIL_SSU PSS where I.classe = PSS.classe and I.anno = PSS.anno and I.nome_sq = PSS.nome_sq ) begin RAISERROR ('La squadra %s non può partecipare con più di tre piloti nell’anno %d e nella classe %d ', 16, 1, @nomesq, @anno, @classe) ROLLBACK TRANSACTION end update PART_PIL set classe=@classe where nomepil=@nome and cognomepil=@cognome and anno=@anno go • TRGpil_su Questo trigger controlla che: 1)che un pilota inserito in una squadra ufficiale in un certo anno non sia già presente in squadre di tipo diverso nello stesso anno; 2)che una squadra ufficiale in un certo anno non possa correre con più di tre piloti. Inoltre si occupa di settare il valore del dato derivato classe nella tabella PART_PIL lasciato a NULL fino all`inserimento di una tupla nella tabella PIL_SU. if exists ( select name from sysobjects where name = 'TRGpil_su' and type = 'tr' ) drop trigger TRGpilsu go create trigger TRGpil_su on pil_su for insert 49 as declare @nome varchar (15) declare @cognome varchar (15) declare @nomesq varchar (15) declare @classe categoria declare @anno annogp select @nome = nomepil, @cognome = cognomepil, @nomesq = nome_sq, @anno=anno, @classe=classe from INSERTED if exists ( select * from PIL_SP PSP, INSERTED I where PSP.anno = I.anno and PSP.nomepil = I.nomepil and PSP.cognomepil = I.cognomepil ) begin RAISERROR ('Il pilota %s %s corre giÀ in una squadra privata di nome %s ', 16, 1, @nome, @cognome, @nomesq) ROLLBACK TRANSACTION end if exists ( select * from PIL_SSU PSS, INSERTED I where PSS.anno = I.anno and PSS.nomepil = I.nomepil and PSS.cognomepil = I.cognomepil ) begin RAISERROR ('Il pilota %s %s corre giá in una squadra semiufficiale di nome %s ', 16, 1, @nome, @cognome, @nomesq) ROLLBACK TRANSACTION end if 3 < ( select count (*) from INSERTED I, PIL_SU PSU where I.classe = PSU.classe and I.anno = PSU.anno and I.nome_sq = PSU.nome_sq ) begin RAISERROR ('La squadra %s non puó partecipare con più di tre piloti nell’anno %d e nella classe %d ', 16, 1, @nomesq, @anno, @classe) ROLLBACK TRANSACTION end update PART_PIL set classe=@classe where nomepil=@nome and cognomepil=@cognome and anno=@anno 50 go Delete Triggers Di seguito sono riportati i triggers che si attivano in cancellazione. • TRDtoglipunti Trigger di cancellazione su CLASSIFICATO. Il trigger aggiorna automaticamente i punti dei piloti nella tabella PART_PIL. Si noti il comportamento speculare rispetto al trigger “TRGaggiornapunti”. if exists ( select name from sysobjects where name = 'TRDtoglipunti' and type = 'TR' ) drop trigger TRDtoglipunti go create trigger TRDtoglipunti on CLASSIFICATO for delete as declare @puntiA tinyint declare @puntiB smallint declare @puntiC smallint declare @nome varchar(30) declare @cognome varchar(30) declare @anno annogp declare @posizione tinyint declare cur cursor for (select nomepil, cognomepil, anno, pos_fin from DELETED) open cur fetch next from cur into @nome, @cognome, @anno, @posizione while (@@fetch_status = 0) begin select @puntiA = punti from PUNTEGGIO where pos_fin = @posizione if (@posizione <= 15) begin select @puntiB = puntip from PART_PIL where nomepil = @nome and cognomepil = @cognome 51 and anno = @anno set @puntiC = @puntiB - @puntiA update PART_PIL set puntip = @puntiC where nomepil = @nome and cognomepil = @cognome and anno = @anno end fetch next from cur into @nome, @cognome, @anno, @posizione end close cur deallocate cur go • TRDdirigente Questi triggers mantengono la totalità della copertura della gerarchia PERSONA in caso di cancellazione. if exists ( select name from sysobjects where name = 'TRDdirigente' and type = 'tr' ) drop trigger TRDdirigente go create trigger TRDdirigente on DIRIGENTE for delete as declare @coddir varchar (20) begin select @coddir = coddirigente from DELETED if not exists ( select * from PILOTA where codpilota = @coddir ) if not exists ( select * from TECNICO where codtecnico = @coddir ) delete from PERSONA where codpers = @coddir end 52 go • TRDpilota if exists ( select name from sysobjects where name = 'TRDpilota' and type = 'tr' ) drop trigger TRDpilota go create trigger TRDpilota on PILOTA for delete as declare @codpil varchar (20) begin select @codpil=codpilota from DELETED if not exists ( select * from TECNICO where codtecnico = @codpil ) if not exists ( select * from dirigente where coddirigente = @codpil ) delete from PERSONA where codpers = @codpil end go • TRDtecnico if exists ( select name from sysobjects where name = 'TRDtecnico' and type = 'tr' ) drop trigger TRDtecnico go 53 create trigger TRDtecnico on TECNICO for delete as declare @codtec varchar (20) begin select @codtec=codtecnico from DELETED if not exists ( select * from PILOTA where codpilota = @codtec ) if not exists ( select * from dirigente where coddirigente = @codtec ) delete from PERSONA where codpers = @codtec end go • TRDgara Trigger di cancellazione su GARA. Una volta mandata in esecuzione la procedura DELdatigara, si può provare a cancellare la corrispondente gara. Se sono presenti dati relativi alle altre classi l’operazione non è permessa; se così non è, il trigger mantiene le cardinalità nell’associazione con LOCAZIONE: cancella le tre tuple relative alle tre classi in GARA e propaga la cancellazione alla corrispondente tupla in LOCAZIONE. if exists ( select name from sysobjects where name = 'TRDgara' and type = 'tr' ) drop trigger TRDgara go create trigger TRDgara on GARA for delete as declare @anno annogp declare @nomecircuito varchar (30) select @anno = anno, @nomecircuito = nomecircuito from DELETED delete from GARA where anno = @anno 54 and nomecircuito = @nomecircuito if exists ( select * from GARA where anno = @anno and nomecircuito = @nomecircuito ) /*Se è presente una o due tuple significa che non sono stati cancellati i dati di tutte le classi, quindi occorre fare il rollback*/ begin RAISERROR ('Impossibile cancellare la gara, controllare che siano stati cancellati i dati relativi alle altre classi.',16,1) ROLLBACK TRANSACTION end else begin delete from LOCAZIONE where anno = @anno and nomecircuito = @nomecircuito end go • TRDlocazione Trigger di cancellazione su LOCAZIONE. Si occupa di verificare la cardinalità minima: se il GP era stato introdotto quell’unico anno, occorre cancellare anche la corrispondente tupla nella tabella GP. if exists ( select name from sysobjects where name = 'TRDlocazione' and type = 'tr' ) drop trigger TRDlocazione go create trigger TRDlocazione on LOCAZIONE for delete as declare @nomegp varchar(20) select @nomegp=nomegp from DELETED if not exists ( select * from LOCAZIONE where nomegp = @nomegp ) delete from GP where nome = @nomegp 55 go • TRDmotouff Triggers di cancellazione su MOTOUFFICIALE e MOTOCLIENTI. Il trigger verifica la cadinalità minima: se la moto cancellata era stata introdotta per quell’unico anno, occorre cancellare anche la corrispondente tupla in MODUFF o MODCLIENTI. Si noti che quest’ultima cancellazione avrà un analogo controllo che potrà portare alla propagazione del processo fino all’entità PRODUZIONE. if exists ( select name from sysobjects where name = 'TRDmotouff' and type = 'TR' ) drop trigger TRDmotouff go create trigger TRDmotouff on MOTOUFFICIALE for delete as declare @sigla varchar(5) declare @classe categoria select @sigla = sigla, @classe = classe from DELETED if not exists ( select * from MOTOUFFICIALE where sigla = @sigla and classe = @classe ) delete from MODUFF where sigla = @sigla and classe = @classe go • TRDmotoclienti if exists ( select name from sysobjects where name = 'TRDmotoclienti' and type = 'TR' ) drop trigger TRDmotoclienti go 56 create trigger TRDmotoclienti on MOTOCLIENTI for delete as declare @sigla varchar(5) declare @classe categoria select @sigla = sigla, @classe = classe from DELETED if not exists ( select * from MOTOCLIENTI where sigla = @sigla and classe = @classe ) delete from MODCLIENTI where sigla = @sigla and classe = @classe go • TRDmoduff Triggers di cancellazione su MODUFF e MODCLIENTI. Il trigger verifica la cadinalità minima: analogamente al caso precedente se il modello cancellato era stato introdotto per quell’unica classe, occorre cancellare anche la corrispondente tupla in PRODUZIONE. Si noti che non è necessario porre vincoli di cancellazione su PRODUZIONE in quanto la consistenza è assicurata dalla foreign key. if exists ( select name from sysobjects where name = 'TRDmoduff' and type = 'TR' ) drop trigger TRDmoduff go create trigger TRDmoduff on MODUFF for delete as declare @sigla varchar(5) select @sigla = sigla from DELETED if not exists ( select * from MODUFF where sigla = @sigla ) delete from PRODUZIONE where sigla = @sigla 57 go • TRDmodclienti if exists ( select name from sysobjects where name = 'TRDmodclienti' and type = 'TR' ) drop trigger TRDmodclienti go create trigger TRDmodclienti on MODCLIENTI for delete as declare @sigla varchar(5) select @sigla = sigla from DELETED if not exists ( select * from MODCLIENTI where sigla = @sigla ) delete from PRODUZIONE where sigla = @sigla go • TRDpart_sq Trigger di cancellazione su PARTSQ. Non è possibile cancellare una tupla nella entità in questione, se non si è prima eliminata la corrispondente nelle sottoentità; quindi il trigger è attivato da uno dei precedenti. Si verifica la cardinalità minima: se la squadra partecipa in quell’unico anno al campionato, occorre cancellare anche la corrispondente tupla in SQUADRA. Si noti che non è necessario porre vincoli di cancellazione su SQUADRA in quanto la consistenza è assicurata dalla foreign key. if exists (select name from sysobjects where name = 'TRDpart_sq' and type = 'tr' ) drop trigger TRDpart_sq go create trigger TRDpart_sq 58 on PART_SQ for delete as declare @nomesq varchar (30) select @nomesq = nome_sq from DELETED if not exists ( select * from PART_SQ where nome_sq = @nomesq ) delete from SQUADRA where nome_sq = @nomesq go • TRDpil_sp Trigger di cancellazione su PIL_SP. Il trigger verifica la cardinalità minima: se il pilota cancellato era l’unico della sua squadra a partecipare a quella classe, occorre aggiornare anche la partecipazione della squadra al campionato. Si noti che la cardinalità minima uguale a zero di PART_PIL nell’associazione è dovuta alla possibilità multipla, si noti inoltre che non ha senso mantenere dati relativi alla partecipazione di un pilota ad un campionato se non legato ad una squadra; per queste considerazioni, risulta opportuno propagare la cancellazione a PART_IN_TEAM e a PART_PIL. if exists ( select name from sysobjects where name = 'TRDpil_sp' and type = 'tr' ) drop trigger TRDpil_sp go create trigger TRDpil_sp on PIL_SP for delete as declare @anno annogp declare @classe categoria declare @nomesq varchar (30) declare @nomepil varchar (30) declare @cognomepil varchar (30) select @anno = anno, @nomepil = nomepil, @cognomepil = cognomepil, @classe = classe, @nomesq = nome_sq from DELETED if not exists ( select * from PIL_SP where classe = @classe 59 and anno = @anno and nome_sq = @nomesq ) begin delete from SP_IN_CLASSE where classe = @classe and anno = @anno and nome_sq = @nomesq end delete from PART_IN_TEAM where anno = @anno and nomepil = @nomepil and cognomepil = @cognomepil delete from PART_PIL where anno = @anno and nomepil = @nomepil and cognomepil = @cognomepil if @@rowcount = 0 begin RAISERROR ('Non è possibile cancellare la partecipazione del pilota %s %s nell’anno %d perché ha partecipato ad una o più gare.',16,1, @nomepil, @cognomepil, @anno) ROLLBACK TRANSACTION end go • TRDpil_ssu Trigger di cancellazione su PIL_SSU. Valgono considerazioni analoghe a quelle precedenti. if exists ( select name from sysobjects where name = 'TRDpil_ssu' and type = 'tr' ) drop trigger TRDpil_ssu go create trigger TRDpil_ssu on PIL_SSU for delete as declare @anno annogp declare @classe categoria declare @nomesq varchar (30) declare @nomepil varchar (30) declare @cognomepil varchar (30) 60 select @anno = anno, @nomepil = nomepil, @cognomepil = cognomepil, @classe = classe, @nomesq = nome_sq from DELETED if not exists ( select * from PIL_SSU where classe = @classe and anno = @anno and nome_sq = @nomesq ) begin delete from SSU_IN_CLASSE where classe = @classe and anno = @anno and nome_sq = @nomesq end delete from PART_IN_TEAM where anno = @anno and nomepil = @nomepil and cognomepil = @cognomepil delete from PART_PIL where anno = @anno and nomepil = @nomepil and cognomepil = @cognomepil if @@rowcount = 0 begin RAISERROR ('Non è possibile cancellare la partecipazione del pilota %s %s nell’anno %d perché ha partecipato ad una o più gare.',16,1, @nomepil, @cognomepil, @anno) ROLLBACK TRANSACTION end go • TRDpil_su Trigger di cancellazione su PIL_SU. if exists ( select name from sysobjects where name = 'TRDpil_su' and type = 'tr' ) drop trigger TRDpil_su go create trigger TRDpil_su on PIL_SU for delete as declare @anno annogp 61 declare @classe categoria declare @nomesq varchar (30) declare @nomepil varchar (30) declare @cognomepil varchar (30) select @anno = anno, @nomepil = nomepil, @cognomepil = cognomepil, @classe = classe, @nomesq = nome_sq from DELETED if not exists ( select * from PIL_SU where classe = @classe and anno = @anno and nome_sq = @nomesq ) begin delete from SU_IN_CLASSE where classe = @classe and anno = @anno and nome_sq = @nomesq end delete from PART_IN_TEAM where anno = @anno and nomepil = @nomepil and cognomepil = @cognomepil delete from PART_PIL where anno = @anno and nomepil = @nomepil and cognomepil = @cognomepil if @@rowcount = 0 begin RAISERROR ('Non è possibile cancellare la partecipazione del pilota %s %s nell’anno %d perché ha partecipato ad una o più gare.',16,1, @nomepil, @cognomepil, @anno) ROLLBACK TRANSACTION end go • TRDsu_in_classe Trigger di cancellazione su SU_IN_CLASSE. Il trigger verifica la cardinalità minima: se la squadra ufficiale partecipa al campionato dell’anno considerato in quell’unica classe, occorre cancellare anche la corrispondente tupla in SQUFFICIALE. A questo punto occorre mantere la copertura totale della gerarchia PARTSQ propagando il processo di cancellazione a PARTSQ; infatti una squadra ufficiale può partecipare in un anno a più classi, ma in tutte deve comparire come ufficiale. if exists ( select name from sysobjects where name = 'TRDsu_in_classe' 62 and type = 'tr' ) drop trigger TRDsu_in_classe go create trigger TRDsu_in_classe on SU_IN_CLASSE for delete as declare @nomesq varchar (30) declare @annoannogp declare @classe categoria select @nomesq = nome_sq, @anno = anno, @classe = classe from DELETED if not exists ( select * from SU_IN_CLASSE where nome_sq = @nomesq and anno = @anno ) begin delete from SQUFFICIALE where nome_sq = @nomesq and anno = @anno delete from PART_SQ where nome_sq = @nomesq and anno = @anno /*Se la cancellazione della tupla in PARTSQ fallisce (sponsorizzazione) non si può accettare la cancellazione in SU_IN_CLASSE*/ if @@rowcount = 0 begin RAISERROR ('Prima occorre cancellare le sponsorizzazioni della squadra.',16,1) ROLLBACK TRANSACTION end end go • TRDssu_in_classe Trigger di cancellazione su SSU_IN_CLASSE. Il trigger verifica la cardinalità minima: se la squadra semiufficiale partecipa al campionato dell’anno considerato in quell’unica classe, occorre cancellare anche la corrispondente tupla in SQSEMIUFF. A questo punto occorre mantere la copertura totale della gerarchia PARTSQ propagando il processo di cancellazione a PARTSQ; infatti una squadra semiufficiale può partecipare in un anno a più classi, ma in tutte deve comparire come semiufficiale. if exists (select name from sysobjects 63 where name = 'TRDssu_in_classe' and type = 'tr' ) drop trigger TRDssu_in_classe go create trigger TRDssu_in_classe on SSU_IN_CLASSE for delete as declare @nomesq varchar (30) declare @anno annogp declare @classe categoria select @nomesq = nome_sq, @anno = anno, @classe = classe from DELETED if not exists ( select * from SSU_IN_CLASSE where nome_sq = @nomesq and anno = @anno ) begin delete from SQSEMIUFF where nome_sq = @nomesq and anno = @anno delete from PART_SQ where nome_sq = @nomesq and anno = @anno /*Se la cancellazione della tupla in PARTSQ fallisce (sponsorizzazione) non si può accettare la cancellazione in SSU_IN_CLASSE*/ if @@rowcount = 0 begin RAISERROR ('Prima occorre cancellare le sponsorizzazioni della squadra.',16,1) ROLLBACK TRANSACTION end end go • TRDsp_in_classe Trigger di cancellazione su SP_IN_CLASSE. Il trigger verifica la cardinalità minima: se la squadra privata partecipa al campionato dell’anno considerato in quell’unica classe, occorre cancellare anche la corrispondente tupla in SQPRIVATA. A questo punto occorre mantere la copertura totale della gerarchia PARTSQ propagando il processo di cancellazione a PARTSQ; infatti una squadra privata può partecipare in un anno a più classi, ma in tutte deve comparire come privata. 64 if exists (select name from sysobjects where name = 'TRDsp_in_classe' and type = 'tr' ) drop trigger TRDsp_in_classe go create trigger TRDsp_in_classe on SP_IN_CLASSE for delete as declare @nomesq varchar (30) declare @anno annogp declare @classe categoria select @nomesq = nome_sq, @anno = anno, @classe = classe from DELETED if not exists ( select * from SP_IN_CLASSE where nome_sq = @nomesq and anno = @anno ) begin delete from SQPRIVATA where nome_sq = @nomesq and anno = @anno delete from PART_SQ where nome_sq = @nomesq and anno = @anno /*Se la cancellazione della tupla in PARTSQ fallisce (sponsorizzazione) non si può accettare la cancellazione in SP_IN_CLASSE.*/ if @@rowcount = 0 begin RAISERROR ('Prima occorre cancellare le sponsorizzazioni della squadra.',16,1) ROLLBACK TRANSACTION end end go Stored Procedures Una “Procedura Memorizzata” è un pacchetto software di codice SQL che viene compilato e memorizzato in SQL Server come singolo modulo e può essere in seguito chiamato ed eseguito. 65 Abbiamo usato questo strumento per implementare le operazioni; abbiamo così creato un’astrazione, dato che tutte le funzionalità che avvengono a basso livello sono astratte dalla persona che chiama la procedura. Tutto ciò che la persona in questione sa è che viene chiamata una procedura, vengono passati alcuni parametri e le tabelle giuste vengono popolate con i dati giusti e nel rispetto dei vincoli. Quando possibile abbiamo preferito implementare i vincoli con triggers piuttosto che con stored procedures, in questo modo è possibile un controllo anche sulle operazioni effettuate dal dbo direttamente sul database. CANCELLAZIONE • DELdatigara La cancellazione dei dati relativi ad una gara risulta essere un’operazione molto delicata con diversi problemi di consistenza dei dati (es: ordine d’arrivo, giro veloce, …); per questo abbiamo negato i diritti di cancellazione sulle tabelle CLASSIFICATO, NONCLASSIFICATO, CLASSIFICA e RECORD. D’altra parte abbiamo ritenuto necessario poter consentire una tale operazione; per questo abbiamo realizzato una procedura che in modo ordinato e un po’ “drastico” azzera tutti i dati introdotti relativamente a una certa gara. Si osservi che la consistenza sui punti dei piloti nel campionato è assicurata dal trigger TRDtoglipunti. if exists ( select name from sysobjects where name = 'DELdatigara' and type = 'p' ) drop procedure DELdatigara go create procedure DELdatigara @classe categoria, @anno annogp, @nomecircuito varchar (30) as declare @nuovotempo char (8) declare @nuovocognome varchar (30) declare @nuovonome varchar (30) declare @nuovacasacostr varchar (15) declare @nuovoanno annogp delete from PENALITA where nomecircuito = @nomecircuito and anno = @anno and classe = @classe delete from CLASSIFICATO where nomecircuito = @nomecircuito and anno = @anno and classe = @classe delete from NONCLASSIFICATO 66 where nomecircuito = @nomecircuito and anno = @anno and classe = @classe delete from CLASSIFICA where nomecircuito = @nomecircuito and anno = @anno and classe = @classe update GARA set tempo = ‘9:99:999’, cognomepil = null, nomepil = null where nomecircuito = @nomecircuito and anno = @anno and classe = @classe /*Occorre controllare che il giro veloce della gara che si intende cancellare non sia anche il record della pista; altrimenti bisogna aggiornare la tabella RECORD*/ if exists ( select * from RECORD where nomecircuito = @nomecircuito and classe = @classe and anno = @anno ) begin select @nuovotempo = tempo, @nuovocognome = cognomepil, @nuovonome = nomepil, @nuovoanno=anno from GARA G where nomecircuito = @nomecircuito and classe = @classe and tempo <= ALL ( select tempo from GARA where nomecircuito = @nomecircuito and classe = @classe) if @@rowcount <> 0 /*Occorre recuperare il dato relativo alla Casa Costruttrice*/ begin select @nuovacasacostr = PR.casa_costr from PIL_SP PSP, SP_IN_CLASSE SPIC, PRODUZIONE PR where @nuovonome = PSP.nomepil and @nuovocognome = PSP.cognomepil and @nuovoanno = PSP.anno and PSP.anno = SPIC.anno and PSP.classe = SPIC.classe and PSP.nome_sq = SPIC.nome_sq and SPIC.sigla = PR.sigla select @nuovacasacostr = PR.casa_costr from PIL_SSU PSS, SSU_IN_CLASSE SSIC, PRODUZIONE PR where @nuovonome = PSS.nomepil and @nuovocognome = PSS.cognomepil and @nuovoanno = PSS.anno 67 and PSS.anno = SSIC.anno and PSS.classe = SSIC.classe and PSS.nome_sq = SSIC.nome_sq and SSIC.sigla = PR.sigla select @nuovacasacostr = PR.casa_costr from PIL_SU PSU, SU_IN_CLASSE SUIC, PRODUZIONE PR where @nuovonome = PSU.nomepil and @nuovocognome = PSU.cognomepil and @nuovoanno = PSU.anno and PSU.anno = SUIC.anno and PSU.classe = SUIC.classe and PSU.nome_sq = SUIC.nome_sq and SUIC.sigla = PR.sigla /*solo una delle tre select da un risultato*/ RAISERROR ('Il nuovo record del circuito %s è %s ',16,1, @nomecircuito, @nuovotempo) update RECORD set nomepil=@nuovonome, cognomepil=@nuovocognome, casa_costr=@nuovacasacostr, anno=@nuovoanno, tempo=@nuovotempo where nomecircuito = @nomecircuito and classe = @classe end else /*Vengono rimessi i valori a NULL in quanto non ci sono altre gare disputate */ update RECORD set nomepil=null, cognomepil=null, casa_costr=null, anno=null, tempo='9:99:999' where nomecircuito= @nomecircuito and classe = @classe end go INSERIMENTI • INScircuito La procedura di inserimento INScircuito si occupa di inserire i dati relativi a un circuito caricando i valori relativi alle tre classi nella tabella RECORD. Il campo tempo viene inizializzato al valore di default. if exists ( select name from sysobjects where name = ’INScircuito’ and type = ‘p’ ) drop procedure INScircuito go 68 create procedure INScircuito @nomecircuito varchar (30), @localita varchar (30), @lunghezza smallint, /*NULL*/ @siglanazione char (3) as begin insert into CIRCUITO (nomecircuito, localita, lunghezza, siglanazione) values (@nomecircuito, @localita, @lunghezza, @siglanazione) insert into RECORD (nomecircuito, classe) values (@nomecircuito, 125) insert into RECORD (nomecircuito, classe) values (@nomecircuito, 250) insert into RECORD (nomecircuito, classe) values (@nomecircuito, 500) end go • INSdirigente Le procedure di inserimento che seguono si occupano di inserire i dati di nuovi piloti, tecnici e dirigenti aggiornando di conseguenza la tabella PERSONA per mantenere la copertura totale della gerarchia. Essendo la gerarchia a copertura sovrapposta prima dell’inserimento nella relazione PERSONA devo controllare se i dati sono già presenti. if exists ( select name from sysobjects where name = 'INSdirigente' and type = 'p' ) drop procedure INSdirigente go create procedure INSdirigente @codpers varchar (20), @nome varchar (30), @cognome varchar (30), @datan smalldatetime, @luogon varchar (30), @nazio char (3) as if exists (select * from PERSONA where codpers=@codpers and (nome <> @nome or cognome <> @cognome or datan <> @datan or luogon <>@luogon /*NULL*/ /*NULL*/ /*NULL*/ 69 or nazionalita <> @nazio)) RAISERROR ('Errore: è già presente nel database una persona con lo stesso codice ma con dati diversi.',16,1) else begin if not exists ( select * from PERSONA where codpers=@codpers ) insert into PERSONA (codpers, nome, cognome, datan, luogon, nazionalita) values (@codpers, @nome, @cognome, @datan, @luogon, @nazio) insert into DIRIGENTE (coddirigente) values (@codpers) end go • INSpilota if exists ( select name from sysobjects where name = 'INSpilota' and type = 'p' ) drop procedure INSpilota go create procedure INSpilota @codpers varchar (20), @nome varchar (30), @cognome varchar (30), @datan smalldatetime, @luogon varchar (30), @nazio char (3) as if exists (select * from PERSONA where codpers=@codpers and (nome <> @nome or cognome <> @cognome or datan <> @datan or luogon <>@luogon or nazionalita <> @nazio)) RAISERROR ('Errore: è già presente nel database una persona con lo stesso codice ma con dati diversi.',16,1) else BEGIN TRANSACTION if not exists ( select * from PERSONA 70 where codpers=@codpers ) insert into PERSONA (codpers, nome, cognome, datan, luogon, nazionalita) values (@codpers, @nome, @cognome, @datan, @luogon, @nazio) insert into PILOTA (codpilota, nome, cognome) values (@codpers, @nome, @cognome) if @@rowcount = 0 begin RAISERROR('Errore: esiste un pilota con lo stesso nome e cognome',16,1) ROLLBACK TRANSACTION end else COMMIT TRANSACTION go • INStecnico if exists ( select name from sysobjects where name = 'INStecnico' and type = 'p' ) drop procedure INStecnico go create procedure INStecnico @codpers varchar (20), @nome varchar (30), @cognome varchar (30), @datan smalldatetime, @luogon varchar (30), @nazio char (3) as if exists ( select * from PERSONA where codpers=@codpers and (nome <> @nome or cognome <> @cognome or datan <> @datan or luogon <>@luogon or nazionalita <> @nazio)) RAISERROR ('Errore: è già presente nel database una persona con lo stesso codice ma con dati diversi.',16,1) else begin if not exists ( select * from PERSONA 71 where codpers=@codpers ) insert into PERSONA (codpers, nome, cognome, datan, luogon, nazionalita) values (@codpers, @nome, @cognome, @datan, @luogon, @nazio) insert into TECNICO (codtecnico) values (@codpers) end go • INSlocazione La procedura di inserimento INSlocazione si occupa dell’inserimento dei dati nella tabella LOCAZIONE. Inoltre per rispettare la cardinalità che lega l’entità LOCAZIONE all’entità GARA inserisce in quest’ultima i dati relativi alle tre diverse classi, lasciando a NULL gli attributi riguardanti il giro veloce. Infine effettua l’inserimento nella tabella GP nel caso il gran premio non sia già stato immesso. if exists (select name from sysobjects where name = ‘INSlocazione’ and type =’p’ ) drop procedure INSlocazione go create procedure INSlocazione @nomecircuito varchar (30), @anno annogp, @nomegp varchar (30), @giorno tinyint, @mese varchar (10), @ngiri125 tinyint, /*NULL*/ @ngiri250 tinyint, /*NULL*/ @ngiri500 tinyint /*NULL*/ as BEGIN TRANSACTION INSLOC if not exists ( select * from GP where nome = @nomegp ) insert into GP (nome) values (@nomegp) insert into LOCAZIONE (nomecircuito, anno, nomegp, giorno, mese) values (@nomecircuito, @anno, @nomegp, @giorno, @mese) if @@rowcount <>0 begin 72 insert into GARA (anno, classe, nomecircuito, numgiri) values (@anno, 125, @nomecircuito, @ngiri125) insert into GARA (anno, classe, nomecircuito, numgiri) values (@anno, 250, @nomecircuito, @ngiri250) insert into GARA (anno, classe, nomecircuito, numgiri) values (@anno, 500, @nomecircuito, @ngiri500) COMMIT TRANSACTION INSLOC end else begin RAISERROR ('Errore: l`inserimento non verrà effettuato.',16,1) ROLLBACK TRANSACTION INSLOC end go • INSmotoufficiale Le procedure di inserimento INSmotoufficiale e INSmotoclienti si occupano dell’inserimento dei dati nelle tabelle MOTOUFFICIALE e MOTOCLIENTI. La procedura mantiene intatti i vincoli di integrita’ dei dati verificando se la nuova moto da inserire è di un modello già presente nel database, se così non è effettua l’inserimento anche nelle tabelle MODUFF e MODCLIENTI. Per analoghi motivi risale eventualmente ad effettuare l’inserimento fino alla tabella PRODUZIONE. if exists ( select name from sysobjects where name = 'INSmotoufficiale' and type = 'p' ) drop procedure INSmotoufficiale go create procedure INSmotoufficiale @sigla varchar (5), @classe categoria, @annop annogp, @cilindrata smallint, @peso tinyint, /*NULL*/ @ncilindri tinyint, /*NULL*/ @casacostr varchar(15) as BEGIN TRANSACTION if not exists ( select * from PRODUZIONE where sigla = @sigla ) insert into PRODUZIONE (sigla, casa_costr) values (@sigla, @casacostr) 73 if not exists ( select * from MODUFF where sigla = @sigla and classe = @classe ) begin insert into MODUFF (sigla, classe, n_cilindri) values (@sigla, @classe ,@ncilindri) if @@rowcount = 0 ROLLBACK TRANSACTION end insert into MOTOUFFICIALE (sigla, classe, annop, cilindrata, peso) values (@sigla, @classe, @annop, @cilindrata, @peso) if @@rowcount <> 0 COMMIT TRANSACTION go • INSmotoclienti if exists ( select name from sysobjects where name = ‘INSmotoclienti’ and type = ‘p’ ) drop procedure INSmotoclienti go create procedure INSmotoclienti @sigla varchar (5), @classe categoria, @annop annogp, @cilindrata smallint, @peso tinyint, /*NULL*/ @ncilindri tinyint, /*NULL*/ @casacostr varchar(15) as BEGIN TRANSACTION if not exists ( select * from PRODUZIONE where sigla = @sigla ) insert into PRODUZIONE (sigla, casa_costr) values (@sigla, @casacostr) if not exists ( select * from MODCLIENTI where sigla = @sigla and classe = @classe 74 ) begin insert into MODCLIENTI (sigla, classe, n_cilindri) values (@sigla, @classe,@ncilindri) if @@rowcount = 0 ROLLBACK TRANSACTION end insert into MOTOCLIENTI (sigla, classe, annop, cilindrata, peso) values (@sigla, @classe, @annop, @cilindrata, @peso) if @@rowcount <> 0 COMMIT TRANSACTION go • INSpart_in_team Procedure di inserimento in PART_IN_TEAM. if exists ( select name from sysobjects where name = 'INSpart_in_team' and type = 'p' ) drop procedure INSpart_in_team go create procedure INSpart_in_team @anno annogp, @cognomepil varchar(30), @nomepil varchar (30), @codice varchar (20), @ruolo varchar (30) as insert into PART_IN_TEAM (anno, cognomepil, nomepil, codtecnico, ruolo ) values (@anno, @cognomepil, @nomepil, @codice, @ruolo) go • INSsp_in_classe La procedure INSsp_in_classe si occupa dell’inserimento dei dati nella tabella SP_IN_CLASSE. La procedura verifica se la partecipazione della squadra privata al campionato, individuata dall’anno e dalla classe passati come parametri, avviene per una squadra già iscritta nello stesso anno in un’altra classe: se così non è effettua l’inserimento nella tabella PART_SQ e successivamente in SQPRIVATA per mantenere intatta l’integrità dei dati: per inserire un dato all’estremità di una gerarchia devo infatti prima aggiornare tutte le entità della gerarchia a livello superiore. Infine si occupa dell’inserimento in SQUADRA nel caso di prima partecipazione assoluta, garantendo così il rispetto della cardinalità minima fra le entità SQUADRA e PART_SQ. Inoltre, per garantire la presenza di almeno un pilota per ogni partecipazione di una squadra al campionato, alla procedura devono essere passati come parametri il nome e il cognome di un pilota e il relativo numero di gara; si provvede poi all’inserimento 75 dei dati relativi al pilota nella tabella PART_PIL e successivamente nella tabella PIL_SP. Tramite una chiamata alla procedura PROCnumgara si controlla che non vi sia già un altro pilota nella classe e nell’anno considerati con lo stesso numero di gara. if exists ( select name from sysobjects where name = 'INSsp_in_classe' and type = 'p' ) drop procedure INSsp_in_classe go create procedure INSsp_in_classe @nomesq varchar(30), @anno annogp, @classe categoria, @sigla varchar(5), @annop annogp, @proprietario varchar(20), @nomepil varchar(30), @cognomepil varchar(30), @numgara tinyint as declare @control tinyint set @control=0 BEGIN TRANSACTION SQPRIV /*Chiamata alla procedura PROCnumgara che controlla non ci sia già un pilota nella stessa classe e nello stesso anno con lo stesso numero di gara del pilota che si deve inserire.*/ EXEC @control = procnumgara @anno, @classe, @numgara if @control=1 begin RAISERROR ('Errore: esiste già un pilota nella classe %d nell`anno %d col numero di gara %d.', 16, 1, @classe, @anno, @numgara) ROLLBACK TRANSACTION SQPRIV end else begin if not exists ( select * from SQUADRA where nome_sq = @nomesq ) insert into SQUADRA (nome_sq) values (@nomesq) if not exists ( select * from PART_SQ where nome_sq = @nomesq and anno = @anno 76 ) begin insert into PART_SQ (nome_sq, anno) values (@nomesq, @anno) insert into SQPRIVATA (nome_sq, anno, coddirsp) values (@nomesq, @anno, @proprietario) if @@rowcount = 0 begin RAISERROR ('Errore: l`inserimento non verrà effettuato.', 16, 1) ROLLBACK TRANSACTION SQPRIV end end if exists ( select * from SP_IN_CLASSE where nome_sq = @nomesq and classe = @classe and anno = @anno ) begin RAISERROR ('La squadra %s è già presente nell`anno %d nella classe %d.', 16, 1, @nomesq, @anno, @classe) ROLLBACK TRANSACTION SQPRIV end else begin insert into SP_IN_CLASSE (nome_sq, anno, classe, sigla, annop) values (@nomesq, @anno, @classe, @sigla, @annop) if @@rowcount =0 begin RAISERROR ('Errore: l`inserimento non verrà effettuato.', 16, 1) ROLLBACK TRANSACTION SQPRIV end else begin insert into PART_PIL (nomepil, cognomepil, anno, numgara) values (@nomepil, @cognomepil, @anno, @numgara) if @@rowcount<>0 begin insert into PIL_SP (nomepil, cognomepil, anno, nome_sq, classe) values (@nomepil, @cognomepil, @anno, @nomesq, @classe) COMMIT TRANSACTION SQPRIV end else begin RAISERROR ('Errore: l`inserimento non verrà effettuato.', 16, 1) ROLLBACK TRANSACTION SQPRIV end 77 end end end go • INSsp_sq Le procedure di inserimento INSsp_sq e INSsp_pil si occupano dell’inserimento dei dati relativi alle sponsorizzazioni delle squadre e dei piloti. In caso di prima sponsorizzazione da parte dello sponsor, si occupa anche dell’inserimento nella tabella sponsor. if exists ( select name from sysobjects where name = 'INSsp_sq' and type = 'p' ) drop procedure INSsp_sq go create procedure INSsp_sq @anno annogp, @nomesq varchar (30), @nomesp varchar (20), @quota int, /*NULL*/ @tipoaz varchar (50) /*NULL*/ as BEGIN TRANSACTION if not exists ( select * from SPONSOR where nome_sp = @nomesp ) insert into SPONSOR (nome_sp, tipo_az) values (@nomesp, @tipoaz) insert into SP_SQ (nome_sp, anno, nome_sq, quota) values (@nomesp, @anno, @nomesq, @quota) if @@rowcount = 0 /*potrebbe fallire per la “RULE” su anno*/ ROLLBACK TRANSACTION else COMMIT TRANSACTION go • INSsp_pil if exists ( select name from sysobjects where name = 'INSsp_pil' and type = 'p' ) 78 drop procedure INSsp_pil go create procedure INSsp_pil @anno annogp, @cognome varchar(30), @nome varchar (30), @nomesp varchar (20), @quota int, @tipoaz varchar (50) as BEGIN TRANSACTION if not exists ( select * from SPONSOR where nome_sp = @nomesp ) insert into SPONSOR (nome_sp, tipo_az) values (@nomesp, @tipoaz) insert into SP_PIL (nome_sp, anno, nomepil, cognomepil, quota) values (@nomesp, @anno, @nome, @cognome, @quota) if @@rowcount = 0 ROLLBACK TRANSACTION else COMMIT TRANSACTION go /*NULL*/ /*NULL*/ • INSssu_in_classe La procedura INSssu_in_classe agisce in modo del tutto identico alla procedura INSsp_in_classe. if exists ( select name from sysobjects where name = 'INSssu_in_classe' and type = 'p' ) drop procedure INSssu_in_classe go create procedure INSssu_in_classe @nomesq varchar(30), @anno annogp, @classe categoria, @sigla varchar(5), @annop annogp, @proprietario varchar(20), @nomepil varchar(30), @cognomepil varchar(30), @numgara tinyint, 79 @priorita bit as declare @control tinyint set @control=0 BEGIN TRANSACTION SQSEMIUFF /*chiamata alla procedura PROCnumgara che controlla non ci sia già un pilota nella stessa classe e nello stesso anno con lo stesso numero di gara del pilota che si deve inserire.*/ EXEC @control = procnumgara @anno, @classe, @numgara if @control=1 begin RAISERROR ('Errore: esiste già un pilota nella classe %d nell`anno %d col numero di gara %d.', 16, 1, @classe, @anno, @numgara) ROLLBACK TRANSACTION SQSEMIUFF end else begin if not exists ( select * from SQUADRA where nome_sq = @nomesq ) insert into SQUADRA (nome_sq) values (@nomesq) if not exists ( select * from PART_SQ where nome_sq = @nomesq and anno = @anno ) begin insert into PART_SQ (nome_sq, anno) values (@nomesq, @anno) insert into SQSEMIUFF (nome_sq, anno, coddirsp) values (@nomesq, @anno, @proprietario) if @@rowcount = 0 begin RAISERROR ('Errore: l`inserimento non verrà effettuato.', 16, 1) ROLLBACK TRANSACTION SQSEMIUFF end end if exists ( select * from SSU_IN_CLASSE where nome_sq = @nomesq and classe = @classe and anno = @anno ) begin RAISERROR ('La squadra %s è già presente nell`anno %d nella classe %d.', 16, 1, @nomesq, @anno, @classe) 80 ROLLBACK TRANSACTION SQSEMIUFF end else begin insert into SSU_IN_CLASSE (nome_sq, anno, classe, sigla, annop) values (@nomesq, @anno, @classe, @sigla, @annop) if @@rowcount =0 begin RAISERROR ('Errore: l`inserimento non verrà effettuato.', 16, 1) ROLLBACK TRANSACTION SQSEMIUFF end else begin insert into PART_PIL (nomepil, cognomepil, anno, numgara) values (@nomepil, @cognomepil, @anno, @numgara) if @@rowcount<>0 begin insert into PIL_SSU (nomepil, cognomepil, anno, nome_sq, classe, priorita) values (@nomepil, @cognomepil, @anno, @nomesq, @classe, @priorita) COMMIT TRANSACTION SQSEMIUFF end else begin RAISERROR ('errore: l`inserimento non verra`effettuato.', 16, 1) ROLLBACK TRANSACTION SQSEMIUFF end end end end go • INSsu_in_classe La procedura di inserimento INSsu_in_classe agisce in modo del tutto identico alla procedura INSsp_in_classe. if exists ( select name from sysobjects where name = 'INSsu_in_classe' and type = 'p' ) drop procedure INSsu_in_classe go create procedure INSsu_in_classe @nomesq varchar(30), @anno annogp, 81 @classe categoria, @sigla varchar(5), @coddirsp varchar(20), @nomepil varchar(30), @cognomepil varchar(30), @numgara tinyint as declare @control tinyint set @control=0 begin transaction squff /*Chiamata alla procedura PROCnumgara che controlla non ci sia già un pilota nella stessa classe e nello stesso anno con lo stesso numero di gara del pilota che si deve inserire.*/ EXEC @control = procnumgara @anno, @classe, @numgara if @control=1 begin RAISERROR ('Errore: esiste già un pilota nella classe %d nell`anno %d col numero di gara %d.', 16, 1, @classe, @anno, @numgara) ROLLBACK TRANSACTION SQUFF end else begin if not exists ( select * from SQUADRA where nome_sq = @nomesq ) insert into SQUADRA (nome_sq) values (@nomesq) if not exists ( select * from PART_SQ where nome_sq = @nomesq and anno = @anno ) begin insert into PART_SQ (nome_sq, anno) values (@nomesq, @anno) insert into SQUFFICIALE (nome_sq, anno) values (@nomesq, @anno) if @@rowcount = 0 begin RAISERROR ('Errore: l`inserimento non verrà effettuato.', 16, 1) ROLLBACK TRANSACTION SQUFF end end if exists (select * from SU_IN_CLASSE where nome_sq = @nomesq and classe = @classe 82 and anno = @anno) begin RAISERROR ('La squadra %s è già presente nell`anno %d nella classe %d.', 16, 1, @nomesq, @anno, @classe) ROLLBACK TRANSACTION SQUFF end else begin insert into SU_IN_CLASSE (nome_sq, anno, classe, sigla, coddirsp) values (@nomesq, @anno, @classe, @sigla, @coddirsp) if @@rowcount =0 begin RAISERROR ('errore: l`inserimento non verra` effettuato.', 16, 1) ROLLBACK TRANSACTION SQUFF end else begin insert into PART_PIL (nomepil, cognomepil, anno, numgara) values (@nomepil, @cognomepil, @anno, @numgara) if @@rowcount<>0 begin insert into PIL_SU (nomepil, cognomepil, anno, nome_sq, classe) values (@nomepil, @cognomepil, @anno, @nomesq, @classe) COMMIT TRANSACTION SQUFF end else begin RAISERROR ('errore: l`inserimento non verra`effettuato.', 16, 1) ROLLBACK TRANSACTION SQUFF end end end end go • INSclassificato La procedura INSclassificato si occupa di inserire i dati relativi ai risultati ottenuti da un pilota in una certa gara. Vengono passati come parametri, fra gli altri, la posizione di partenza e quella di arrivo del pilota in questione e la procedura si occupa di effettuare l’inserimento nella tabella CLASSIFICA e in seguito in CLASSIFICATO, garantendo la copertura totale ed esclusiva della gerarchia. L’ultimo parametro rappresenta il tempo del giro veloce effettuato dal pilota nella gara; si noti che la procedura inserisce in GARA solo il più basso tra quelli inseriti (giro veloce della gara). if exists ( select name from sysobjects where name = 'INSclassificato' 83 and type = 'p' ) drop procedure INSclassificato go create procedure INSclassificato @nomepil varchar (30), @cognomepil varchar (30), @classe categoria, @anno annogp, @nomecircuito varchar (30), @pos_iniz tinyint, @pos_fin tinyint, @tempo char (8) as BEGIN TRANSACTION insert into CLASSIFICA (nomepil, cognomepil, classe, anno, nomecircuito, pos_iniz) values (@nomepil, @cognomepil, @classe, @anno, @nomecircuito, @pos_iniz) if @@rowcount=0 begin RAISERROR ('Errore: l’inserimento non verrà effettuato.',16,1) ROLLBACK TRANSACTION end else begin insert into CLASSIFICATO (nomepil, cognomepil, anno, classe, nomecircuito, pos_fin) values (@nomepil, @cognomepil, @anno, @classe, @nomecircuito, @pos_fin) if @@rowcount = 0 begin RAISERROR ('Errore: l`inserimento non verrà effettuato.',16,1) ROLLBACK TRANSACTION end else begin if (@tempo is not null) if exists ( select * from GARA where classe=@classe and anno=@anno and nomecircuito=@nomecircuito and @tempo<tempo ) update GARA set tempo = @tempo, nomepil = @nomepil, cognomepil = @cognomepil where nomecircuito = @nomecircuito and classe = @classe 84 and anno = @anno COMMIT TRANSACTION end end go • INSnonclassificato La procedura di inserimento in NONCLASSIFICATO si occupa di inserire i dati relativi a un pilota che non ha portato a termine la gara. Vengono passati come parametri, fra gli altri, la posizione di partenza e la causa del ritiro del pilota in questione e la procedura si occupa di effettuare l’inserimento nella tabella CLASSIFICA e in seguito in NONCLASSIFICATO, garantendo la copertura totale ed esclusiva della gerarchia. if exists ( select name from sysobjects where name = ‘INSnonclassificato' and type = 'p' ) drop procedure INSnonclassificato go create procedure INSnonclassificato @nomepil varchar (30), @cognomepil varchar (30), @classe categoria, @anno annogp, @nomecircuito varchar (30), @causarit varchar (30), @numgiro tinyint, @motivosq varchar (30), @pos_iniz tinyint as begin insert into CLASSIFICA (nomepil, cognomepil, classe, anno, nomecircuito, pos_iniz) values (@nomepil, @cognomepil, @classe, @anno, @nomecircuito, @pos_iniz) if @@rowcount<>0 insert into NONCLASSIFICATO (nomepil, cognomepil, anno, classe, nomecircuito, numgiro, causarit, motivosq) values (@nomepil, @cognomepil, @anno, @classe, @nomecircuito, @numgiro, @causarit, @motivosq) else RAISERROR ('Errore: l’inserimento non verrà effettuato.',16,1) end go 85 • INSpenalita La procedura di inserimento in PENALITA si occupa di inserire le penalità inflitte a un pilota durante una gara. Vengono passati come parametri, fra gli altri, il giro in cui la penalità è stata inflitta, la causa di tale provvedimento e il tipo di penalità prevista. Si noti che, potendo essere presenti più penalità per un pilota in una certa gara, la procedura si occupa di settare la variabile numord che servirà ad identificare la penalità inserita nel database. if exists ( select name from sysobjects where name = 'INSpenalita' and type = 'p' ) drop procedure INSpenalita go create procedure INSpenalita @nomepil varchar (30), @cognomepil varchar (30), @nomecircuito varchar (30), @anno annogp, @classe categoria, @causa varchar (30), @numgiro tinyint, @tipo varchar (30) as declare @numord tinyint if not exists ( select * from PENALITA where nomepil = @nomepil and cognomepil = @cognomepil and anno = @anno and nomecircuito = @nomecircuito ) set @numord=1 else begin set @numord = ( select max(nordine) from PENALITA where nomepil = @nomepil and cognomepil = @cognomepil and anno = @anno and nomecircuito = @nomecircuito ) 86 set @numord = @numord + 1 end if exists ( select * from CLASSIFICA where nomepil = @nomepil and cognomepil = @cognomepil and anno = @anno and classe = @classe and nomecircuito = @nomecircuito ) insert into PENALITA (anno, classe, nomecircuito, cognomepil, nomepil, nordine, causa, numgiro, tipo) values (@anno, @classe, @nomecircuito, @cognomepil, @nomepil, @numord, @causa, @numgiro, @tipo) else RAISERROR ('Impossibile inserire la penalità, il pilota cui si fa riferimento non compare nella classifica.', 16, 1) go • INSpil_sp La procedura d’inserimento INSpil_sp si occupa dell’ inserimento dei dati nella tabella PIL_SP dopo aver controllato, tramite una chiamata alla procedura PROCnumgara, che non vi sia gia’ un altro pilota nella classe e nell’anno considerati con lo stesso numero di gara e dopo aver effettuato l’inserimento nella tabella PART_PIL dei dati riguardanti il pilota che si vuole inserire. if exists ( select name from sysobjects where name = 'INSpil_sp' and type = 'p' ) drop procedure INSpil_sp go create procedure INSpil_sp @nomepil varchar (30), @cognomepil varchar (30), @anno annogp, @nome_sq varchar (30), @classe categoria, @numgara tinyint as declare @control tinyint set @control=0 if exists ( select * from PART_PIL where nomepil = @nomepil and cognomepil = @cognomepil 87 and anno = @anno ) RAISERROR ('Errore: il pilota è già inserito in una squadra per l’anno %d.', 16, 1, @anno) else begin /*Chiamata alla procedura PROCnumgara che controlla non ci sia gia` un pilota nella stessa classe e nello stesso anno con lo stesso numero di gara del pilota che si deve inserire.*/ EXEC @control = procnumgara @anno, @classe, @numgara if @control=1 RAISERROR ('Errore: esiste già un pilota nella classe %d nell`anno %d col numero di gara %d.', 16, 1, @classe, @anno, @numgara) else if not exists ( select * from pilota where nome = @nomepil and cognome = @cognomepil ) RAISERROR ('Errore: i dati del pilota non sono stati immessi.',16,1) else BEGIN TRANSACTION insert into PART_PIL ( nomepil, cognomepil, numgara, anno) values (@nomepil, @cognomepil, @numgara, @anno) insert into PIL_SP ( nomepil, cognomepil, anno, classe, nome_sq) values (@nomepil, @cognomepil, @anno, @classe, @nome_sq) if @@rowcount<>0 COMMIT TRANSACTION else begin RAISERROR ('Errore: la squadra %s non è presente nel database relativamente all`anno %d e alla classe %d.', @nome_sq, @anno, @classe, 16,1) ROLLBACK TRANSACTION end end go • INSpil_ssu La procedura di inserimento INSpil_ssu agisce in modo del tutto identico alla procedura INSpil_sp. if exists ( select name from sysobjects where name = 'INSpil_ssu' and type = 'p' ) drop procedure INSpil_ssu go 88 create procedure INSpil_ssu @nomepil varchar (30), @cognomepil varchar (30), @anno annogp, @nome_sq varchar (30), @classe categoria, @numgara tinyint, @priorita bit as declare @control tinyint set @control=0 if exists ( select * from PART_PIL where nomepil = @nomepil and cognomepil = @cognomepil and anno = @anno ) RAISERROR ('Errore: il pilota è già inserito in una squadra per l’anno %d.', 16, 1, @anno) else begin /*Chiamata alla procedura PROCnumgara che controlla non ci sia già un pilota nella stessa classe e nello stesso anno con lo stesso numero di gara del pilota che si deve inserire.*/ EXEC @control = procnumgara @anno, @classe, @numgara if @control=1 RAISERROR ('Errore: esiste già un pilota nella classe %d nell`anno %d col numero di gara %d.', 16, 1, @classe, @anno, @numgara) else if not exists ( select * from PILOTA where nome = @nomepil and cognome = @cognomepil ) RAISERROR ('Errore: i dati del pilota non sono stati immessi.',16,1) else BEGIN TRANSACTION insert into PART_PIL ( nomepil, cognomepil, numgara, anno) values (@nomepil, @cognomepil, @numgara, @anno) insert into PIL_SSU ( nomepil, cognomepil, anno, classe, nome_sq, priorita) values (@nomepil, @cognomepil, @anno, @classe, @nome_sq, @priorita) if @@rowcount<>0 COMMIT TRANSACTION else begin RAISERROR ('Errore: la squadra %s non è presente nel database relativamente all`anno %d e alla classe %d.', @nome_sq, @anno, @classe, 16,1) ROLLBACK TRANSACTION end 89 end go • INSpil_su La procedura di inserimento INSpil_su agisce in modo del tutto identico alla procedura INSpil_sp. if exists ( select name from sysobjects where name = 'INSpil_su' and type = 'p' ) drop procedure INSpil_su go create procedure INSpil_su @nomepil varchar (30), @cognomepil varchar (30), @anno annogp, @nome_sq varchar (30), @classe categoria, @numgara tinyint as declare @control tinyint set @control=0 if exists ( select * from PART_PIL where nomepil = @nomepil and cognomepil = @cognomepil and anno = @anno ) RAISERROR (' errore: il pilota è già inserito in una squadra per l’anno %d.', 16, 1, @anno) else begin /*Chiamata alla procedura PROCnumgara che controlla non ci sia già un pilota nella stessa classe e nello stesso anno con lo stesso numero di gara del pilota che si deve inserire.*/ EXEC @control = procnumgara @anno, @classe, @numgara if @control=1 RAISERROR ('Errore: esiste già un pilota nella classe %d nell`anno %d col numero di gara %d.', 16, 1, @classe, @anno, @numgara) else if not exists ( select * from PILOTA where nome = @nomepil and cognome = @cognomepil ) RAISERROR ('errore: i dati del pilota non sono stati immessi.',16,1) 90 else BEGIN TRANSACTION insert into PART_PIL ( nomepil, cognomepil, numgara, anno) values (@nomepil, @cognomepil, @numgara, @anno) insert into PIL_SU ( nomepil, cognomepil, anno, classe, nome_sq) values (@nomepil, @cognomepil, @anno, @classe, @nome_sq) if @@rowcount<>0 COMMIT TRANSACTION else begin RAISERROR ('errore: la squadra %s non e` presente nel database relativamente all`anno %d e alla classe %d.', @nome_sq, @anno, @classe, 16,1) ROLLBACK TRANSACTION end end go • PROCnumgara Questa procedura controlla che non sia già presente nel database un pilota che corre con il numero di gara passato come parametro nello stesso anno e nella stessa classe considerati: ritorna il valore 0 se non trova nulla, il valore 1 se trova un altro pilota con lo stesso numero. if exists ( select name from sysobject where name = ‘PROCnumgara’ and type = ‘p’ ) drop procedure PROCnumgara go create procedure PROCnumgara @anno annogp, @classe categoria, @numgara tinyint as begin if exists ( select * from PART_PIL where anno=@anno and classe=@classe and numgara = @numgara ) return 1 else return 0 end 91 go IMPLEMENTAZIONE DELLE OPERAZIONI RICHIESTE DA SPECIFICHE NOTA: Nelle tabelle del database sono stati inseriti solo i dati di stretto interesse al fine di poter eseguire e verificare le operazioni, pertanto sono presenti alcune incoerenze e una quantita’di dati limitata. Operazione 1: l’ inserimento dei dati di una gara non viene implementato mediante una procedura ma viene realizzato direttamente in visual basic mediante una maschera che richiama le seguenti procedure di inserimento : PRINSCLASSIF, PRINSNONCLASSIF,PRINSPENALITA . • proccreatmp La procedura proccreatmp ha il compito di creare una tabella temporanea ##TMP e riempirla con i nomi dei piloti presenti nel database riportando, per ognuno degli anni in cui il pilota ha partecipato al campionato mondiale, la classe in cui ha corso, i punti conquistati e il numero di vittorie ottenute in quella stagione. Tale tabella viene poi utilizzata da diverse store procedures, dedicate all’implementazione delle operazioni richiete da specifiche, tramite previa chiamata alla procedura proccreatmp. Si noti l`utilizzo di un cursore che recupera le tuple della tabella PART_PIL che in seguito serviranno per riempire la tabella temporanea. if exists ( select name from sysobjects where name = 'proccreatmp' ) drop procedure proccreatmp go create procedure proccreatmp as declare @nome varchar (30) declare @cognome varchar (30) declare @punti smallint declare @vittorie tinyint declare @anno annogp declare @classe categoria create table ##TMP ( anno smallint, check (anno>=1980), classe smallint, check ((classe=125) or (classe=250) or (classe=500)), 92 nomepil varchar (30) not null, cognomepil varchar (30) not null, puntip smallint null, n_vitt tinyint null constraint primarytmp primary key (nomepil, cognomepil, anno) ) /*dichiarazione del cursore cur_class*/ declare cur_class cursor for select anno, classe, nomepil, cognomepil, puntip from PART_PIL order by puntip desc open cur_class fetch next from cur_class into @anno, @classe, @nome, @cognome, @punti while (@@fetch_status=0) begin select @vittorie = count (*) from CLASSIFICATO where anno=@anno and classe=@classe and nomepil=@nome and cognomepil=@cognome and pos_fin=1 insert into ##TMP (anno, classe, nomepil, cognomepil, puntip, n_vitt) values (@anno, @classe, @nome, @cognome, @punti, @vittorie) fetch next from cur_class into @anno, @classe, @nome, @cognome, @punti end close cur_class deallocate cur_class go • PROCclasspil Questa procedura si occupa di visualizzare la classifica dei piloti relativamente all’anno e alla classe passati come parametri. Accanto ai nomi dei piloti vengono riportati i punti conquistati e il numero di vittorie ottenute. Si noti che la procedura sfrutta i dati contenuti nella tabella temporanea ##TMP, creata e riempita dalla procedura proccreatmp, invocata all’inizio del codice. if exists ( select name from sysobjects where name = 'PROCclasspil' ) drop procedure PROCclasspil go 93 create procedure PROCclasspil @anno annogp, @classe categoria as exec proccreatmp select nomepil, cognomepil, puntip, n_vitt from ##TMP where anno=@anno and classe=@classe order by puntip desc, n_vitt desc drop table ##TMP go • PROCclassificacc Questa procedura si occupa di visualizzare la classifica delle case costruttrici relativamente all’anno e alla classe passati come parametri in ingresso. Accanto ad ogni casa costruttrice vengono riportati i punti ottenuti nelle varie gare del campionato. if exists (select * from sysobjects where name = ‘PROCclassificacc’ and type = ‘p’ ) drop procedure PROCclassificacc go CREATE procedure PROCclassificacc @anno annogp, @classe categoria as if @classe = 125 select casa_costr, sum(punti) as punti from VEWsq125 VS, VEWpil125 VP where VS.nome_sq=VP.nome_sq and VS.anno=VP.anno and VS.anno=@anno group by casa_costr order by 2 desc else if @classe = 250 select casa_costr, sum(punti) as punti from VEWsq250 VS, VEWpil250 VP where VS.nome_sq=VP.nome_sq and VS.anno=VP.anno and VS.anno=@anno group by casa_costr order by 2 desc else 94 if @classe = 500 select casa_costr, sum(punti) as punti from VEWsq500 VS, VEWpil500 VP where VS.nome_sq=VP.nome_sq and VS.anno=VP.anno and VS.anno=@anno group by casa_costr order by 2 desc go • PROCclassparz Questa procedura si occupa di visualizzare la classifica parziale dei piloti relativamente all’anno e alla classe passati come parametri in ingresso. Accanto al nominativo di ogni pilota vengono riportati i diversi circuiti su cui si sono disputate gare del campionato con i relativi punti ottenuti e infine la somma totale dei punti in classifica. Si noti che la procedura sfrutta i dati contenuti nella tabella temporanea ##TMP, creata e riempita dalla procedura proccreatmp, invocata all’inizio del codice. if exists (select * from sysobjects where name = ‘PROCclassparz’ and type = ‘p’ ) drop procedure PROCclassparz go create procedure Procclassparz @anno annogp, @classe categoria as (select P.cognomepil, P.nomepil, G.nomecircuito, PU.punti, P.puntip from PART_PIL P, GARA G, CLASSIFICATO CL, PUNTEGGIO PU where G.anno=@anno and G.classe=@classe and CL.anno=G.anno and CL.classe=G.classe and CL.nomecircuito=G.nomecircuito and P.nomepil=CL.nomepil and P.cognomepil=CL.cognomepil and P.anno=@anno and CL.pos_fin=PU.pos_fin) union (select P.cognomepil, P.nomepil, G.nomecircuito, 0, P.puntip from PART_PIL P, GARA G where P.anno=@anno and P.classe=@classe and G.anno=P.anno and P.classe=G.classe 95 and not exists (select * from CLASSIFICATO CL where CL.nomepil=P.nomepil and CL.cognomepil=P.cognomepil and CL.nomecircuito=G.nomecircuito and Cl.anno=G.anno) ) order by 5 desc, 1, 2, 3 go • PROCalbodoro Questa procedura implementa l’operazione N. 3, ossia la visualizzazione dell’albo d’oro relativamente agli anni precedenti all’anno che viene passato come parametro in ingresso. Per ogni classe vengono riportati i nomi dei piloti che hanno vinto almeno un mondiale con affianco il numero dei mondiali vinti nella classe considerata. Si noti che la procedura sfrutta i dati contenuti nella tabella temporanea ##TMP, creata e riempita dalla procedura proccreatmp, invocata all’inizio del codice. if exists ( select name from sysobjects where name = 'PROCalbodoro' ) drop procedure PROCalbodoro go create procedure PROCalbodoro @anno annogp as exec proccreatmp print 'CLASSE 125' /*Albo d'oro classe 125*/ (select nomepil, cognomepil, count(*) as mondiali_vinti from ##TMP P where classe=125 and anno<@anno and not exists( select * from ##TMP P1 where P.anno=P1.anno and P.classe=P1.classe and P.puntip<P1.puntip) and not exists ( select * from ##TMP P2 where P.anno=P2.anno and P.classe=P2.classe and P.puntip=P2.puntip and P.n_vitt<P2.n_vitt) and anno=any( select anno from PART_PIL 96 where nomepil=P.nomepil and cognomepil=P.cognomepil) group by P.nomepil, P.cognomepil) order by 3 desc, cognomepil, nomepil print 'CLASSE 250' /*Albo d'oro classe 250 */ (select nomepil, cognomepil, count(*) as mondiali_vinti from ##TMP P where classe=250 and anno < @anno and not exists( select * from ##TMP P1 where P.anno=P1.anno and P.classe=P1.classe and P.puntip<P1.puntip) and not exists ( select * from ##TMP P2 where P.anno=P2.anno and P.classe=P2.classe and P.puntip=P2.puntip and P.n_vitt<P2.n_vitt) and anno=any( select anno from PART_PIL where nomepil=P.nomepil and cognomepil=P.cognomepil) group by P.nomepil, P.cognomepil) order by 3 desc, cognomepil, nomepil print 'CLASSE 500' /*Albo d'oro classe 500*/ (select nomepil, cognomepil, count(*) as mondiali_vinti from ##TMP P where classe=500 and anno < @anno and not exists( select * from ##TMP P1 where P.anno=P1.anno and P.classe=P1.classe and P.puntip<P1.puntip) and not exists ( select * from ##TMP P2 where P.anno=P2.anno and P.classe=P2.classe and P.puntip=P2.puntip and P.n_vitt<P2.n_vitt) and anno=any( select anno from PART_PIL where nomepil=P.nomepil and cognomepil=P.cognomepil) group by P.nomepil, P.cognomepil) 97 order by 3 desc, cognomepil, nomepil go • PROCcarrierapil Questa procedura implementa una operazione abbastanza articolata: la visualizzazione della carriera di un pilota il cui nominativo viene passato come parametro in ingresso. Vengono riportate in uscita diverse informazioni: nell’ordine si visualizza per ogni classe il numero di gare vinte, di gare disputate e di giri veloci compiuti; segue la posizione finale raggiunta in classifica al termine di ogni anno in cui il pilota ha partecipato al campionato e infine il numero di mondiali vinti in ogni classe. Si noti che per ottenere alcuni di questi risultati la procedura sfrutta i dati contenuti nella tabella temporanea ##TMP, creata e riempita dalla procedura proccreatmp, invocata in alcuni punti del codice. if exists ( select name from sysobjects where name = 'PROCcarrierapil' ) drop procedure PROCcarrierapil go create procedure PROCcarrierapil @nome varchar(30), @cognome varchar(30) as declare @cont125 tinyint declare @cont250 tinyint declare @cont500 tinyint declare @classe categoria set @cont125=0 set @cont250=0 set @cont500=0 /*Utilizzo il cursore cur_vitt per recuperare le tuple dalla tabella CLASSIFICATO e contare le vittorie di un pilota nelle tre diverse classi aumentando il valore dei relativi contatori dichiarati come variabili*/ declare cur_vitt cursor for ( select classe /*op 4a) numero di vittorie*/ from CLASSIFICATO where nomepil=@nome and cognomepil=@cognome and pos_fin=1 ) open cur_vitt fetch next from cur_vitt into @classe while (@@fetch_status = 0) begin if @classe=125 98 set @cont125= @cont125 + 1 if @classe=250 set @cont250=@cont250 + 1 if @classe = 500 set @cont500=@cont500 + 1 fetch next from cur_vitt into @classe end RAISERROR ('Il pilota %s %s ha vinto %d gare nella classe 125, %d nella classe 250 e %d nella classe 500',16,1,@nome,@cognome,@cont125, @cont250, @cont500) close cur_vitt deallocate cur_vitt select @cont125=count(*) /*op 4b) numero di gara fatte*/ from CLASSIFICA where nomepil=@nome and cognomepil=@cognome and classe=125 select @cont250=count(*) from CLASSIFICA where nomepil=@nome and cognomepil=@cognome and classe=250 select @cont500=count(*) from CLASSIFICA where nomepil=@nome and cognomepil=@cognome and classe=500 RAISERROR ('Il pilota %s %s ha partecipato a %d gare nella classe 125, a %d gare nella classe 250 e a %d gare nella classe 500',16,1,@nome, @cognome, @cont125, @cont250, @cont500) select @cont125=count(*) /*OP 4c) numero di giri veloci*/ from GARA where nomepil=@nome and cognomepil=@cognome and classe=125 select @cont250=count(*) from GARA where nomepil=@nome and cognomepil=@cognome and classe=250 select @cont500=count(*) from GARA where nomepil=@nome and cognomepil=@cognome and classe=500 RAISERROR ('Il pilota %s %s ha fatto %d giri veloci nella classe 125, %d nella classe 250, %d nella classe 500',16,1,@nome, @cognome, @cont125, @cont250, @cont500) 99 exec proccreatmp (select anno, classe, count(*)+1 as posizione_finale /*OP 4d)posizione in classifica al */ from ##TMP P /*termine del campionato per ogni anno*/ where exists( select * from ##TMP P1 where nomepil=@nome and cognomepil=@cognome and P.anno=P1.anno and P.classe=P1.classe and P.puntip>P1.puntip) or exists ( select * from ##TMP P2 where nomepil=@nome and cognomepil=@cognome and P.anno=P2.anno and P.classe=P2.classe and P.puntip=P2.puntip and P.n_vitt>P2.n_vitt) and anno=any( select anno from PART_PIL where nomepil=@nome and cognomepil=@cognome) group by P.anno, P.classe) union (select anno, classe, 1 as posizione_finale from ##TMP P where nomepil=@nome and cognomepil=@cognome and not exists( select * from ##TMP P1 where P.anno=P1.anno and P.classe=P1.classe and P.puntip<P1.puntip) and not exists ( select * from ##TMP P2 where P.anno=P2.anno and P.classe=P2.classe and P.puntip=P2.puntip and P.n_vitt<P2.n_vitt) and anno=any( select anno from PART_PIL where nomepil=@nome and cognomepil=@cognome) group by P.anno, P.classe) order by anno, classe drop table ##TMP exec proccreatmp /*OP 4e) mondiali vinti*/ 100 select @cont125=count(*) from ##TMP P where nomepil=@nome and cognomepil=@cognome and classe=125 and not exists( select * from ##TMP P1 where P.anno=P1.anno and P.classe=P1.classe and P.puntip<P1.puntip) and not exists ( select * from ##TMP P2 where P.anno=P2.anno and P.classe=P2.classe and P.puntip=P2.puntip and P.n_vitt<P2.n_vitt) and anno=any( select anno from PART_PIL where nomepil=@nome and cognomepil=@cognome) select @cont250=count(*) from ##TMP P where nomepil=@nome and cognomepil=@cognome and classe=250 and not exists( select * from ##TMP P1 where P.anno=P1.anno and P.classe=P1.classe and P.puntip<P1.puntip) and not exists ( select * from ##TMP P2 where P.anno=P2.anno and P.classe=P2.classe and P.puntip=P2.puntip and P.n_vitt<P2.n_vitt) and anno=any( select anno from PART_PIL where nomepil=@nome and cognomepil=@cognome) select @cont500=count(*) from ##TMP P where nomepil=@nome and cognomepil=@cognome and classe=500 and not exists( select * 101 from ##TMP P1 where P.anno=P1.anno and P.classe=P1.classe and P.puntip<P1.puntip) and not exists ( select * from ##TMP P2 where P.anno=P2.anno and P.classe=P2.classe and P.puntip=P2.puntip and P.n_vitt<P2.n_vitt) and anno=any( select anno from PART_PIL where nomepil=@nome and cognomepil=@cognome) RAISERROR ('Il pilota %s %s ha vinto %d mondiali nella classe 125, %d nella classe 250, %d nella classe 500.',16,1, @nome, @cognome, @cont125, @cont250, @cont500) drop table ##TMP go • PROCvisualcircuito Questa procedura implementa l’operazione N.5: visualizza i dati di un circuito il cui nome è passato come parametro in ingresso. Vengono riportati anche i record stabiliti sul circuito relativamente alle tre diverse classi. if exists (select * from sysobjects where name = ‘PROCvisualcircuito’ and type = ‘p’) drop procedure PROCvisualcircuito go create procedure PROCvisualcircuito @nome varchar(30) as select * from CIRCUITO where nomecircuito = @nome print 'Visualizzazione del record del circuito nelle tre classi' print '' select * from RECORD where nomecircuito = @nome and classe = 125 select * from RECORD where nomecircuito = @nome and classe = 250 select * 102 from RECORD where nomecircuito = @nome and classe = 500 go VISTE Riportiamo di seguito il codice di sei viste che sono state utilizzate per l’ implementazione delle operazioni. Queste prime tre: VEWpil125, VEWpil250, VEWpil500, a seconda della cilindrata riportano i seguenti dati: per ogni anno in cui un pilota ha gareggiato nome e cognome del pilota, nomesq, punti. If exists ( select table_name From information_schema.views Where table_name = ‘VEWpil125’ ) drop view VEWpil125 go create view VEWpil125 (anno, cognome, nome, nomesq, punti) as select PP.anno, PP.cognomepil, PP.nomepil, PSP.nome_sq, PP.puntip from PIL_SP PSP, PART_PIL PP where PSP.classe = 125 and PP.anno = PSP.anno and PP.cognomepil=PSP.cognomepil and PP.nomepil=PSP.nomepil UNION select PP.anno, PP.cognomepil, PP.nomepil, PSU.nome_sq, PP.puntip from PIL_SU PSU, PART_PIL PP where PSU.classe = 125 and PP.anno=PSU.anno and PP.cognomepil=PSU.cognomepil and PP.nomepil=PSU.nomepil UNION select PP.anno, PP.cognomepil, PP.nomepil, PSS.nome_sq, PP.puntip from PIL_SSU PSS, PART_PIL PP where PSS.classe = 125 and PP.anno=PSS.anno and PP.cognomepil=PSS.cognomepil and PP.nomepil=PSS.nomepil go If exists ( select table_name 103 From information_schema.views Where table_name = ‘VEWpil250’ ) drop view VEWpil250 go create view VEWpil250 (anno, cognome, nome, nomesq, punti) as select PP.anno, PP.cognomepil, PP.nomepil, PSP.nome_sq, PP.puntip from PIL_SP PSP, PART_PIL PP where PSP.classe = 250 and PP.anno=PSP.anno and PP.cognomepil=PSP.cognomepil and PP.nomepil=PSP.nomepil UNION select PP.anno, PP.cognomepil, PP.nomepil, PSU.nome_sq, PP.puntip from PIL_SU PSU, PART_PIL PP where PSU.classe = 250 and PP.anno=PSU.anno and PP.cognomepil=PSU.cognomepil and PP.nomepil=PSU.nomepil union select PP.anno, PP.cognomepil, PP.nomepil, PSS.nome_sq, PP.puntip from PIL_SSU PSS, PART_PIL PP where PSS.classe = 250 and PP.anno=PSS.anno and PP.cognomepil=PSS.cognomepil and PP.nomepil=PSS.nomepil go If exists ( select table_name From information_schema.views Where table_name = ‘VEWpil500’ ) drop view VEWpil500 go create view VEWpil500 (anno, cognome, nome, nomesq, punti) as select PP.anno, PP.cognomepil, PP.nomepil, PSP.nome_sq, PP.puntip from PIL_SP PSP, PART_PIL PP where PSP.classe = 500 and PP.anno=PSP.anno and PP.cognomepil=PSP.cognomepil and PP.nomepil=PSP.nomepil 104 UNION select PP.anno, PP.cognomepil, PP.nomepil, PSU.nome_sq, PP.puntip from PIL_SU PSU, PART_PIL PP where PSU.classe = 500 and PP.anno=PSU.anno and PP.cognomepil=PSU.cognomepil and PP.nomepil=PSU.nomepil UNION select PP.anno, PP.cognomepil, PP.nomepil, PSS.nome_sq, PP.puntip from PIL_SSU PSS, PART_PIL PP where PSS.classe = 500 and PP.anno=PSS.anno and PP.cognomepil=PSS.cognomepil and PP.nomepil=PSS.nomepil go Queste ultime tre: VEWsq125, VEWsq250, VEWsq500 a seconda della cilindrata forniscono per ogni anno in cui una squadra ha partecipato al campionato del mondo: il nome della squadra appunto e la casa cotruttrice che le forniva le moto If exists ( select table_name From information_schema.views Where table_name = ‘VEWsq125’ ) drop view VEWsq125 go create view VEWsq125 (anno, nomesq, casacostr) as select SPIC.anno, SPIC.nome_sq, PR.casa_costr from SP_IN_CLASSE SPIC, PRODUZIONE PR where SPIC.classe=125 and SPIC.sigla=PR.sigla UNION select SSC.anno, SSC.nome_sq, PR.casa_costr from SSU_IN_CLASSE SSC, PRODUZIONE PR where SSC.classe=125 and SSC.sigla=PR.sigla UNION select SUIC.anno, SUIC.nome_sq, PR.casa_costr from SU_IN_CLASSE SUIC, PRODUZIONE PR where SUIC.classe=125 and SUIC.sigla=PR.sigla go If exists ( select table_name 105 From information_schema.views Where table_name = ‘VEWsq250’ ) drop view VEWsq250 go create view VEWsq250 (anno, nomesq, casacostr) as select SPIC.anno, SPIC.nome_sq, PR.casa_costr from SP_IN_CLASSE SPIC, PRODUZIONE PR where SPIC.classe=250 and SPIC.sigla=PR.sigla UNION select SSC.anno, SSC.nome_sq, PR.casa_costr from SSU_IN_CLASSE SSC, PRODUZIONE PR where SSC.classe=250 and SSC.sigla=PR.sigla UNION select SUIC.anno, SUIC.nome_sq, PR.casa_costr from SU_IN_CLASSE SUIC, PRODUZIONE PR where SUIC.classe=250 and SUIC.sigla=PR.sigla go If exists ( select table_name From information_schema.views Where table_name = ‘VEWsq500’ ) drop view VEWsq500 go create view VEWsq500 (anno, nomesq, casacostr) as select SPIC.anno, SPIC.nome_sq, PR.casa_costr from SP_IN_CLASSE SPIC, PRODUZIONE PR where SPIC.classe=500 and SPIC.sigla=PR.sigla UNION select SSC.anno, SSC.nome_sq, PR.casa_costr from SSU_IN_CLASSE SSC, PRODUZIONE PR where SSC.classe=500 and SSC.sigla=PR.sigla UNION select SUIC.anno, SUIC.nome_sq, PR.casa_costr from SU_IN_CLASSE SUIC, PRODUZIONE PR where SUIC.classe=500 106 and SUIC.sigla=PR.sigla go CONNESSIONE ODBC Per la comunicazione tra la base di dati in SQL SERVER 7.0 ed il front-end realizzato in VISUAL BASIC sfruttando la libreria degli oggetti DAO, e’ necessario utilizzare uno strumento che consenta di tradurre il linguaggio di un sistema nell’altro. Nel nostro caso la scelta e’caduta su ODBC acronimo di Open Data Base Connectivity che e’uno standard di interconnessione fra sistemi. La base di dati in SQL SERVER rappresenta la fonte dati ODBC; VISUAL BASIC comunichera’direttamente con ODBC e quindi indirettamente con la base di dati. Per settare l’interfacciamento tra i due componenti e’necessaria l’impostazione dal pannello di controllo della fonte dati ODBC. INTERFACCIA GRAFICA IN AMBIENTE VISUAL BASIC Si intendono illustrare brevemente le metodologie di accesso ai dati utilizzate per permettere agli utenti di dialogare tramite l’ interfaccia con la base di dati. Sono stati utilizzati per implementare l’ interfaccia gli oggetti della libreria DAO (Data Acces Object) e mediante ODBC e’ stato possibile accedere a fonti di dati remoti in modo totalmente trasparente. DAO e’ una gerarchia di oggetti e collezioni che ci fornisce un’ interfaccia ad oggetti finalizzata all’ accesso ai dati. Ogni oggetto dispone di proprieta’ che ne caratterizzano lo stato e di metodi che eseguono le azioni sull’oggetto stesso. L’oggetto padre DBEngine contiene il motore Jet Database Engine. Il DBEngine dispone di una collezione di workspace, cioe’ aree di lavoro nelle quali si possono creare e aprire database. La filosofia DAO e’ che ogni oggetto possiede una collezione di oggetti che nella gerarchia si trovano ad un livello inferiore. Per esempio l’oggetto Database dispone di una collezione di Recordset che sua volta ha al suo interno una collezione di oggetti Field. Per convenzione le collezioni di oggetti hanno lo stesso nome dell’ oggetto al plurale(es: Recordset,Recordsets). Gli oggetti fondamentali della gerarchia sono : DATABASE, incapsula un database, locale o remoto che sia. RECORDSET, e’una vista logica specificata da una tabella di selezione SQL. I metodi di un oggetto recordset permettono svariate operazioni sui dati ( inserimenti, ricerche, aggiornamenti etc. ). Un recordset equivale a cio’che nel corso di BASI DI DATI viene chiamato CURSORE. 107 TABLEDEF, e’l’oggetto che racchiude informazioni sulla struttura delle tabelle. RELATION, tiene traccia delle relazioni logiche che intercorrono tra le entita’del database. INDEX, consente di associare indici alle tabelle per migliorare le prestazioni in fase di retrieval. QUERYDEF, permette di definire e memorizzare query SQL utili alle applicazioni per interagire con il database. La strutturazione di un sistema basato su DAO consente un’interfaccia uniforme per accessi locali e remoti. Ne schematizziamo un modello di tipo CLIENT-SERVER. CLIENT APPLICAZIONE DAO JET ODBC SERVER SQL Server 7.0 DB 108 GESTIONE DELLA SICUREZZA Per garantire la sicurezza della base di dati, si è prevista l’esistenza di tre diverse tipologie di utente che possono accedere alle informazioni memorizzate con differenti diritti: • Utente è l’utente generico, che accede alla base di dati per ottenere informazioni di vario genere sul campionato dell’anno in corso o di anni precedenti. Pertanto ha il diritto di effettuare qualunque tipo di selezione e di eseguire le procedure che implementano operazioni di visualizzazione dei dati (classifiche, carriera di un pilota, etc…) • Manutentore è colui che si occupa di mantenere aggiornata la base di dati, ossia di inserire gli esiti delle varie gare del campionato in corso e tutte le nuove partecipazioni di squadre, piloti, etc… all’inizio di ogni nuovo campionato. Pertanto ha il diritto di eseguire tutte le procedure di inserimento (non può effettuare direttamente l’istruzione INSERT ma deve usare le procedure predisposte) e le cancellazioni (ha diritto di usare l’istruzione DELETE su quasi tutte le tabelle, per le altre sono previste procedure di cancellazione oppure esistono appositi trigger per mantenere coerenti le informazioni) • Db_owner è un utente che deve possedere una conoscenza completa della struttura della base di dati. Ha infatti gli stessi diritti del db_creator, ossia può effettuare qualsiasi tipo di inserimento, cancellazione, aggiornamento e selezione sulle tabelle. Ad ogni tipologia di utente viene assegnata una nuova login, ossia uno username e una password, che lo identificheranno al momento dell’accesso alle informazioni. All’atto della creazione di questa login, viene selezionato o creato un profilo utente (ROLE) da assegnare alla tipologia di utente in questione; proprio definendo le caratteristiche di questo ROLE il db_creator può indicare quali sono le operazioni che l’utente è autorizzato a compiere. All’avvio dell’applicazione si presenta la seguente maschera di login in cui si devono inserire username e password differenti per le tipologie di utente previste : utente manutentore Username: utente Username: manutentore Password: utente Password: manutentore 109 Nel riconoscimento dell’utente all’interno del codice della maschera di login abbiamo appositamente trascurato il fatto che facendo un confronto diretto tra i valori immessi per loggarsi e quelli di username e password degli utenti abilitati, si lascia una traccia nel client che permetterebbe ad un intruso che riescisse a violare il codice del client di accedere al server sfruttando le informazioni messegli a disposizione da questo confronto. La cosa migliore sarebbe cercare di mettere tutte le informazioni relative a username e password all’interno del server ed accedervi ad esempio mediante stored procedure. Abbiamo preferito non appesantire il nostro elaborato oltre quanto era richiesto. Ai due tipi di utente si presentano due maschere differenti: Maschera opzione1 per l’utente generico e opzione2 per il manutentore in cui compaiono le operazioni consentite: sono state implementate solo alcune delle operazioni a cui l’utente ha diritto 110 . CODICE DI ALCUNE DELLE MASCHERE IMPLEMENTATE - Maschera di login con controllo dello username e delle password Private Sub inizia_Click() If Text1.Text = "utente" Then If Text2.Text = "utente" Then opzione1.Show vbModal Else MsgBox "password non valida", vbInformation, "Messaggio di informazione" Text2.Text = "" End If Else If Text1.Text = "manutentore" Then If Text2.Text = "manutentore" Then opzione2.Show vbModal Else MsgBox "password non valida", vbInformation, "Messaggio di informazione" Text2.Text = "" End If Else MsgBox "username non valido", vbInformation, "Messaggio di informazione" Text1.Text = "" End If End If End Sub 111 Maschera per l’inserimento dei dati di una gara (implementa l’operazione 1) Public work As Workspace Public db As Database Const stringadiconnessione = "ODBC;DSN=connettimanutentore;UID=DUE;PWD=pippo2000;" Private Sub boxanno_Change() Combocircuito.Clear If Len(boxanno.Text) = 4 Then Dim istr As String Dim rs As Recordset istr = "select nomecircuito from LOCAZIONE where anno= " & boxanno.Text & "" Set rs = db.OpenRecordset(istr, dbOpenDynaset) rs.MoveFirst Do While Not rs.EOF Combocircuito.AddItem rs!nomecircuito rs.MoveNext Loop End If End Sub Private Sub command_Click() Unload insgara End Sub Private Sub Form_Load() Dim istr As String On Error GoTo errore 'connessione OBDC Set work = DBEngine.CreateWorkspace("odbcwork", "DUE", "", dbUseODBC) DBEngine.DefaultType = dbUseODBC Set db = Workspaces(0).OpenDatabase("connection", dbDriverNoPrompt, False, stringadiconnessione) ‘L`inserimento deve iniziare dal primo classificato 112 classificato.pos = 1 Comboclasse.AddItem "125" Comboclasse.AddItem "250" Comboclasse.AddItem "500" Exit Sub errore: 'errore di connessione If Err.Number = 3146 Then MsgBox Errors(0).Description, vbExclamation, "Messaggio di sistema" End If Unload insgara End Sub Private Sub inserisci_Click() If ((Len(boxanno.Text) <> 4) Or (boxanno.Text < 1980)) Then MsgBox "Anno inserito non valido", vbInformation, "Messaggio di informazione" Else If class.Value Then 'nascondo i campi relativi all'inserimento di un pilota non classificato e visualizzo l'intestazione ‘relativa all'inserimento dei classificati classificato.titoloc1.Visible = True classificato.titoloc2.Visible = True classificato.boxpos.Visible = True classificato.titolonon.Visible = False classificato.boxcausarit.Visible = False classificato.causarit.Visible = False classificato.boxmotivosq.Visible = False classificato.motivosq.Visible = False classificato.aggiunta.Visible = False classificato.tempo.Caption = "tempo" classificato.Show vbModal Else If nonclass.Value Then ‘visualizzo i campi relativi all'inserimento di un pilota non classificato e nascondo l'intestazione ‘ relativa all'inserimento dei classificati classificato.titoloc1.Visible = False classificato.titoloc2.Visible = False classificato.boxpos.Visible = False classificato.titolonon.Visible = True classificato.boxcausarit.Visible = True classificato.causarit.Visible = True classificato.boxmotivosq.Visible = True classificato.motivosq.Visible = True classificato.aggiunta.Visible = True classificato.tempo.Caption = "ultimo giro" 113 classificato.Show vbModal Else MsgBox "Selezionare un pulsante di opzione", vbInformation, "Messaggio di informazione" End If End If End If End Sub - Maschera per l’inserimento dei piloti classificati, non classificati e delle penalita’ loro inflitte Public pos As Integer Public work As Workspace Public db As Database Const stringadiconnessione = "ODBC;DSN=connettimanutentore;UID=DUE;PWD=pippo2000;" Private Sub Form_Load() 'connessione OBDC Set work = DBEngine.CreateWorkspace("odbcwork", "DUE", "", dbUseODBC) DBEngine.DefaultType = dbUseODBC Set db = Workspaces(0).OpenDatabase("connection", dbDriverNoPrompt, False, stringadiconnessione) boxpos.Text = pos End Sub Private Sub Inspenalita_Click() Dim proc As String 'stringa che contiene l 'istruzione sql Dim rs As Recordset On Error GoTo errore proc = "exec prinspenalita '" & boxnome.Text & "','" & boxcognome.Text & "','" & insgara.Combocircuito.Text & "'," & insgara.boxanno.Text & "," & insgara.Comboclasse.Text & ",'" & boxmotivo.Text & "'," & boxngiro.Text & ",'" & boxtipo.Text & "'" Set rs = db.OpenRecordset(proc, dbOpenDynaset) MsgBox "Inserimento avvenuto correttamente", vbInformation, "Messaggio di informazione" boxmotivo.Text = "" boxtipo.Text = "" boxngiro.Text = "" 114 Exit Sub errore: If Err.Number = 3146 Then MsgBox Errors(0).Description, vbExclamation, "Messaggio di sistema" End If End Sub Private Sub Inspilota_Click() Dim variabile As String Dim proc As String 'stringa che contiene l 'istruzione sql Dim rs As Recordset On Error GoTo errore If insgara.Class.Value Then If boxtempo.Text = "" Then variabile = "null" Else variabile = "'" & boxtempo.Text & "'" End If proc = "exec prinsclassif '" & boxnome.Text & "', '" & boxcognome.Text & "', " & insgara.Comboclasse.Text & ", " & insgara.boxanno.Text & ", '" & insgara.Combocircuito.Text & "', " & boxpos_iniz.Text & ", " & boxpos.Text & ", " & variabile Set rs = db.OpenRecordset(proc, dbOpenDynaset) MsgBox "Inserimento avvenuto correttamente", vbInformation, "Messaggio di informazione" pos = pos + 1 End If If insgara.nonclass.Value Then If boxmotivosq.Text = "" Then variabile = "null" Else variabile = "'" & boxmotivosq.Text & "'" End If proc = "exec prinsnonclassif '" & boxnome.Text & "', '" & boxcognome.Text & "', " & insgara.Comboclasse.Text & ", " & insgara.boxanno.Text & ", '" & insgara.Combocircuito.Text & "', '" & boxcausarit.Text & "'," & boxtempo.Text & ", " & variabile & "," & boxpos_iniz.Text Set rs = db.OpenRecordset(proc, dbOpenDynaset) MsgBox "Inserimento avvenuto correttamente", vbInformation, "Messaggio di informazione" End If ‘Disabilito i campi relativi ai dati del pilota inserito per permettere l'inserimento di eventuali ‘penalita' 115 boxnome.Enabled = False boxcognome.Enabled = False boxpos_iniz.Enabled = False boxtempo.Enabled = False boxcausarit.Enabled = False boxmotivosq.Enabled = False Exit Sub errore: If Err.Number = 3146 Then MsgBox Errors(0).Description, vbExclamation, "Messaggio di sistema" End If End Sub Private Sub Inssuccessivo_Click() 'resetto e riabilito i campi relativi ai dati del pilota boxnome.Text = "" boxcognome.Text = "" boxpos_iniz.Text = "" boxtempo.Text = "" boxcausarit.Text = "" boxmotivosq.Text = "" boxnome.Enabled = True boxcognome.Enabled = True boxpos_iniz.Enabled = True boxtempo.Enabled = True boxcausarit.Enabled = True boxmotivosq.Enabled = True boxpos.Text = pos End Sub Private Sub Menuprec_Click() 'resetto e riabilito i campi relativi ai dati del pilota boxnome.Text = "" boxcognome.Text = "" boxpos_iniz.Text = "" boxtempo.Text = "" boxcausarit.Text = "" boxmotivosq.Text = "" boxnome.Enabled = True 116 boxcognome.Enabled = True boxpos_iniz.Enabled = True boxtempo.Enabled = True boxcausarit.Enabled = True boxmotivosq.Enabled = True ‘Torno al menu’ precedente classificato.Hide End Sub - Maschera per la visualizzazione delle classifiche (implementa l’operazione 2) Public work As Workspace Public db As Database Public rs As Recordset Public proc As String Const stringadiconnessione = "ODBC;DSN=connettiutente;UID=UNO;PWD=pippo2000;" Private Sub classcc_Click() If ((Len(boxannoc.Text) <> 4) Or (boxannoc.Text < 1980)) Then MsgBox "Anno inserito non valido", vbInformation, "Messaggio di informazione" Else List1.Visible = False List2.Visible = True List2.Clear List3.Visible = True List3.Clear Label1.Caption = "" Label2.Caption = "casa costruttrice" Label3.Caption = "punti" Label4.Caption = "" Label5.Caption = "" On Error GoTo errore proc = "exec procclassificacc " & boxannoc.Text & "," & comboclassec.Text & "" Set rs = db.OpenRecordset(proc, dbOpenDynaset) Do While Not rs.EOF List2.AddItem rs!casacostr List3.AddItem rs!punti rs.MoveNext 117 Loop End If Exit Sub errore: If Err.Number = 3146 Then MsgBox Errors(0).Description, vbExclamation, "Messaggio di sistema" End If End Sub Private Sub classparz_Click() Dim firsttime As Boolean Dim nome As String Dim cognome As String Dim spazio1 As Integer Dim spazio2 As Integer Dim pilota As String Dim circuito As String Dim i As Integer Dim punti As Integer Dim puntitot As Integer If ((Len(boxannoc.Text) <> 4) Or (boxannoc.Text < 1980)) Then MsgBox "Anno inserito non valido", vbInformation, "Messaggio di informazione" Else List1.Visible = True List1.Clear List2.Visible = False List3.Visible = False Label1.Caption = "pilota" Label2.Caption = "circuito" Label3.Caption = "punti" Label4.Caption = "punti" Label5.Caption = "totali" On Error GoTo errore proc = "exec procclassparz " & boxannoc.Text & "," & comboclassec.Text & "" Set rs = db.OpenRecordset(proc, dbOpenDynaset) If Not rs.EOF Then nome = rs!nomepil cognome = rs!cognomepil firsttime = True 118 End If Do While Not rs.EOF If ((firsttime <> True) And ((nome = rs!nomepil) And (cognome = rs!cognomepil))) Then circuito = rs!nomecircuito spazio2 = 25 - Len(circuito) For i = 1 To spazio2 circuito = circuito & " " Next pilota = "" For i = 1 To 25 pilota = pilota & " " Next punti = rs!punti If punti < 10 Then List1.AddItem pilota & " " & circuito & " " & punti Else List1.AddItem pilota & " " & circuito & " " & punti End If Else pilota = rs!nomepil & " " & rs!cognomepil spazio1 = 25 - Len(pilota) For i = 1 To spazio1 pilota = pilota & " " Next circuito = rs!nomecircuito spazio2 = 25 - Len(circuito) For i = 1 To spazio2 circuito = circuito & " " Next puntitot = rs!puntip punti = rs!punti If punti < 10 Then List1.AddItem pilota & " " & circuito & " " & punti & " " & puntitot Else List1.AddItem pilota & " " & circuito & " " & punti & " " & puntitot End If firsttime = False End If nome = rs!nomepil cognome = rs!cognomepil rs.MoveNext Loop End If Exit Sub errore: 119 If Err.Number = 3146 Then MsgBox Errors(0).Description, vbExclamation, "Messaggio di sistema" End If End Sub Private Sub classpil_Click() If ((Len(boxannoc.Text) <> 4) Or (boxannoc.Text < 1980)) Then MsgBox "Anno inserito non valido", vbInformation, "Messaggio di informazione" Else List1.Visible = False List2.Visible = True List2.Clear List3.Visible = True List3.Clear Label1.Caption = "" Label2.Caption = "pilota" Label3.Caption = "punti" Label4.Caption = "" Label5.Caption = "" On Error GoTo errore proc = "exec procclasspil " & boxannoc.Text & "," & comboclassec.Text & "" Set rs = db.OpenRecordset(proc, dbOpenDynaset) Do While Not rs.EOF List2.AddItem rs!nomepil & " " & rs!cognomepil List3.AddItem rs!puntip rs.MoveNext Loop End If Exit Sub errore: If Err.Number = 3146 Then MsgBox Errors(0).Description, vbExclamation, "Messaggio di sistema" End If End Sub 120 Private Sub command_Click() Unload Visualclassifica End Sub Private Sub Form_Load() List1.Visible = False List2.Visible = False List3.Visible = False Label1.Caption = "" Label2.Caption = "" Label3.Caption = "" Label4.Caption = "" Label5.Caption = "" On Error GoTo errore 'connessione OBDC Set work = DBEngine.CreateWorkspace("odbcwork", "UNO", "", dbUseODBC) DBEngine.DefaultType = dbUseODBC Set db = Workspaces(0).OpenDatabase("connection", dbDriverNoPrompt, False, stringadiconnessione) comboclassec.AddItem "125" comboclassec.AddItem "250" comboclassec.AddItem "500" Exit Sub errore: 'errore di connessione If Err.Number = 3146 Then MsgBox Errors(0).Description, vbExclamation, "Messaggio di sistema" End If Unload Visualclassifica End Sub Visualizzazione dati di un circuito Public work As Workspace Public db As Database Const stringadiconnessione = "ODBC;DSN=connettiutente;UID=UNO;PWD=pippo2000;" Private Sub Command1_Click() Unload visualcirc 121 End Sub Private Sub Form_Load() Label6.Caption = "" Label7.Caption = "" Label8.Caption = "" Label9.Caption = "" Label2.Caption = "" Label11.Caption = "" Label13.Caption = "" Label14.Caption = "" Label15.Caption = "" On Error GoTo errore 'connessione ODBC Set work = DBEngine.CreateWorkspace("odbcwork", "UNO", "", dbUseODBC) DBEngine.DefaultType = dbUseODBC Set db = Workspaces(0).OpenDatabase("connection", dbDriverNoPrompt, False, stringadiconnessione) Dim rs As Recordset Dim istr As String istr = "SELECT nomecircuito FROM circuito" Set rs = db.OpenRecordset(istr, dbOpenDynaset) Do While Not rs.EOF nome.AddItem rs!nomecircuito rs.MoveNext Loop Exit Sub errore: If Err.Number = 3146 Then MsgBox Errors(0).Description, vbExclamation, "Messaggio di sistema" End If Unload visualcirc End Sub Private Sub visualizza_Click() Dim tempo As String Dim rs As Recordset Dim rz As Recordset Dim rv As Recordset Set rs = db.OpenRecordset("SELECT * FROM circuito WHERE nomecircuito = '" & nome.Text & "'", dbOpenDynaset) Set rz = db.OpenRecordset("SELECT * FROM record WHERE nomecircuito = '" & nome.Text & "'", dbOpenDynaset) Set rv = db.OpenRecordset("SELECT giorno,mese,anno FROM locazione WHERE nomecircuito = '" & nome.Text & "'", dbOpenDynaset) localita.Text = "" lunghezza.Text = "" nazione.Text = "" 122 'rendo visibili le label e le caselle di testo in cui compariranno i risultati gare.Visible = True gare.Clear record.Visible = True record.Clear Label6.Caption = "giorno" Label7.Caption = "mese" Label8.Caption = "anno" Label9.Caption = "GARE DISPUTATE" Label2.Caption = "classe" Label11.Caption = " pilota" Label13.Caption = "anno" Label14.Caption = "casa costruttrice" Label15.Caption = "tempo" If Not rs.EOF Then localita.Text = rs!localita nazione.Text = rs!siglanazione If Not IsNull(rs!lunghezza) Then lunghezza.Text = rs!lunghezza End If Else MsgBox "il circuito non e'presente nel db ", vbInformation, "messaggio di informazione" End If If Not rv.EOF Then Do While Not rv.EOF gare.AddItem rv!giorno & " " & rv!mese & " rv.MoveNext Loop Else gare.AddItem "non si sono disputate gare" End If rv.Close Do While Not rz.EOF If rz!tempo = "9:99:999" Then tempo = "non effettuato" Else tempo = rz!tempo End If record.AddItem rz!classe & " " & tempo & " rz!casa_costr & " " & rz!anno rz.MoveNext Loop rz.Close " & rv!anno " & rz!cognomepil & " " & rz!nomepil & " " & End Sub Indice Introduzione 1 123 Specifiche Operazioni Volume dei dati Progettazione concettuale Schema E/R completo Traduzione dello schema E/R in schema relazionale Dato derivato Implementazione delle tabelle Triggers Dolete triggers Stored procedures Implementazione delle operazioni Viste Connessione ODBC Interfaccia grafica Gestione della sicurezza Maschere Codice maschere Bibliografia Bibliografia • Moto Sprint • Progetto di Basi di Dati Relazionali (Beneventano, Bergamaschi, Vincini) • SQL Server 7 di Anthoni T.Mann • www.dorna.com società organizzatrice del mondiale • Visual Basic e la programmazione dei database (Naccarato) 124 1 2 3 3 4 15 19 22 31 48 62 87 97 101 101 103 103 105 115