dicembre 2004 - Posts
Mi è successo di incappare in un errore riportato da SQL Server che - a mio
modesto parere - se non è un buco, è quantomeno poco chiaro e rischia di
trarre in inganno il DBA.
L'errore si presenta nelle situazioni in cui si definiscono diverse relazioni di
integrità referenziale residenti sulla stessa tabella ed aventi come
"padre" una seconda tabella.
I books on line parlano di una situazione analoga a quella che vado a descrivere
solo nel caso di relazioni autoreferenzianti. In quel caso è vero che la
situazione può prendere strade che portano ad un loop, ma in questo caso no!
Ma vediamo di capire bene di cosa stò parlando con un esempio...
Supponiamo di avere due tabelle (Ordini ed Utenti) aventi la struttura
riportata di seguito:
use [Test_DB]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Ordini]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Ordini]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Utenti]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Utenti]
GO
CREATE TABLE [dbo].[Ordini] (
[OrdineID] [int] NOT NULL ,
[Data] [datetime] NULL ,
[UtenteInserimento] [int] NULL ,
[UtenteAggiornamento] [int] NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Utenti] (
[UtenteID] [int] NOT NULL ,
[Cognome] [varchar] (50) COLLATE Latin1_General_CI_AS NULL ,
[Nome] [varchar] (50) COLLATE Latin1_General_CI_AS NULL
) ON [PRIMARY]
GO
|
Una volta definite le tabelle, passiamo ad inserire le
loro chiavi primarie.
ALTER TABLE [dbo].[Ordini] WITH NOCHECK ADD
CONSTRAINT [PK_Ordini] PRIMARY KEY CLUSTERED
(
[OrdineID]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Utenti] WITH NOCHECK ADD
CONSTRAINT [PK_Utenti] PRIMARY KEY CLUSTERED
(
[UtenteID]
) ON [PRIMARY]
GO
|
A questo punto, diciamo che vogliamo porre qualche
vincolo allo scopo di garantire l'integrità dei dati presenti nelle due
tabelle. La cosa più semplice e lineare sarebbe quella di inserire due foreign
key per i campi UtenteInserimento ed UtenteAggiornamento vincolandoli al campo
UtenteID della relativa tabella.
Se però vogliamo dare ai nostri utenti la possibilità di modificare il
codice identificativo degli utenti anche dopo che essi hanno inserito e/o
aggiornato degli ordini, la definizione delle relazioni dovrà essere come
riportato di seguito:
ALTER TABLE [dbo].[Ordini] ADD
CONSTRAINT [FK_Ordini_Utenti_Inserimento] FOREIGN KEY
(
[UtenteInserimento]
) REFERENCES [dbo].[Utenti] (
[UtenteID]
) ON DELETE CASCADE ON UPDATE CASCADE
GO
ALTER TABLE [dbo].[Ordini] ADD
CONSTRAINT [FK_Ordini_Utenti_Aggiornamento] FOREIGN KEY
(
[UtenteAggiornamento]
) REFERENCES [dbo].[Utenti] (
[UtenteID]
) ON DELETE CASCADE ON UPDATE CASCADE
GO
|
Specificando le clausole ON DELETE e ON UPDATE, infatti,
si ottiene la propagazione automatica su tutti i record referenziati delle
eventuali modifiche e cancellazioni svolte sulla tabella Utenti.
Se, però, si esegue quest'ultima parte dello script si
ottiene il seguente errore:
Server: messaggio 1785, livello 16, stato 1, riga 1
L'impostazione del vincolo FOREIGN KEY 'FK_Ordini_Utenti_Aggiornamento' nella tabella 'Ordini' potrebbe generare cicli o percorsi a catena multipla. Specificare ON DELETE NO ACTION oppure ON UPDATE NO ACTION oppure modificare altri vincoli FOREIGN KEY.
Server: messaggio 1750, livello 16, stato 1, riga 1
Impossibile creare il vincolo. Vedere gli errori precedenti.
Analizzando la struttura (molto semplice, per la verità) delle due tabelle,
è evidente che non c'è alcun rischio di ciclo o di percorso multiplo come
invece ci segnala l'RDBMS di Microsoft.
Soluzione
Non esiste una soluzione "pulita": SQL Server 2000 non supporta questo
tipo di integrità referenziale "multipla". Faccio notare, però, che
RDBMS come Oracle o addirittura Access la supportano tranquillamente...
L'unica scappatoia che si presenta è quella di implmentare l'integrità
referenziale non con le relazioni bensì con i trigger.
Lorenzo Braidi
...acquista "Database Design",
il mio primo libro!

Prosegue anche questo mese il corso di Database Design che stò tenendo su DEV nella rubrica "Programmo subito". Questa lezione è interamente dedicata alle viste: cosa sono, a cosa servono, come si definiscono, ecc.

Nei mesi scorsi (a Marzo) è uscito su Visual Basic Journal un mio articolo che illustrava l'algoritmo che si utilizza per calcolare e validare un codice fiscale. Allegato a quell'articolo c'era anche un piccolo
progettino VB6 che implementava l'algoritmo oggetto dell'articolo. Un lettore si è accorto di un caso particolare nel quale il software suddetto non funzionava correttamente. Nel numero di VBJ di questo mese è pubblicata la correzione di tale baco. Comunque, il software che si scarica dal sito FTP di Infomedia è stato immediatamente corretto...
In diverse occasioni mi è capitato di incappare in un errore dell'Agent di SQL Server 2000 al quale, fino ad ora, non avevo mai trovato soluzione.
L'errore si verifica quando si eseguono Job aventi più di uno step. C'è da dire che il malfunzionamento, però, si verifica solo se si utilizza la versione italiana di SQL Server 2000 con applicato il service pack 3a (SP3a). In queste condizioni si ottiene la chiusura del servizio e si ritrova nell'event log la seguente segnalazione
Exception 5 caught at line 191 of file ..\src\refreshr.c. SQLServerAgent initiating self-termination.
Leggendo un po' in giro, era abbastanza chiaro che l'errore fosse tipico solo della versione italiana, quindi ci ho provato... et voila!
Per risolvere la questione è abbastanza sostituire il componente che genera il problema con quello della versione inglese: sostituire cioé il file sqlagent.rll presente nella sub-directory 1040 con quello omonimo della directory 1033.
Lorenzo

Dopo tanta attesa (almeno da parte mia...), finalmente il mio primo libro ha visto la luce!
Rispetto ad una versione che avevo pubblicato su questo stesso blog, la copertina ha subito qualche piccola variazione, ma la sostanza non cambia.
Potete trovare tutte le informazioni relative al libro qui
Scarica il PDF del sommario dettagliato!
Qualche nota tecnica:
Pagine: 400 circa
Formato: 17 x 24
ISBN: 88-481-1694-9
Prezzo: solo :-) € 29,90
Buona lettura
E'
da pochi giorni disponibile la nuova versione di PostgreSQL:
il DBMS freeware che - stando alle anticipazioni - dovrebbe essere il primo ad
aderire appieno allo standard SQL:2003. La versione che si può
tranquillamente e gratuitamente scaricare dal sito FTP
Italiano è, in realtà, la Release Candidate 1 (Versione 8.0.0 RC1). La
versione definitiva è prevista per la seconda metà del mese... in ogni caso,
prima di Natale.
Lorenzo
Un lettore mi ha segnalato un sito
nel quale è possibile trovare gratuitamente una certa mole di documenti
riguardanti Oracle. Sono di particolare interesse quelli relativi al tuning del
database... Molto interessante!
Buona lettura
Ho visto su alcuni blog tecnici che il mio articolo sulla paginazione dei dati pubblicato su Computer Programming n. 140
(Novembre 2004) sta facendo discutere.
Innanzitutto riporto di seguito la schematizzazione della soluzione che propongo nell'articolo così come l'ha rielaborata
Riccardo Golia nel suo blog
SELECT TOP [dimPage] [field1], [field2], ...
FROM (SELECT TOP [dimPage] [field1], [field2], ...
FROM (SELECT TOP ([dimPage] * [numPage]) [field1], [field2], ...
FROM [table]
WHERE [conditions]
ORDER BY [order] ASC) AS TMP1
ORDER BY [order] DESC) AS TMP2
ORDER BY [order] ASC
I principali dubbi che ho avuto modo di leggere riguardano
in particolare la possibilità di ottenere il numero totale dei record che fanno
parte del resultset che si vuole trattare. Perché si possa ottenere tale
informazione è necessario eseguire a priori una select count che estragga il
dato e successivamente si procederà con l'estrazione della pagina desiderata.
Faccio notare che, come detto anche nell'articolo, se non
si conosce a priori il numero totale dei record coinvolri, non è possibile
sfruttare in maniera ottimale la soluzione descritta.
Se per esempio abbiamo una tabella con 100 record e li vogliamo visualizzare
raggruppati in pagine da 20 righe ciascuna, il risultato sarà corretto per le
prime 5. Nel momento, però, in cui richiederemo la visualizzazione della sesta
pagina, otterremo gli stessi venti record che comparivano nella quinta. Questo
comportamento può essere evitato solo se si conosce a priori il numero totale
dei record e così anche il numero di pagine che ne deriveranno.
In un commento di uno dei blog che ho letto, poi, veniva
proposta una soluzione alternativa. La soluzione prevede sostanzialmente di
estrarre con una query tutti i record che si vogliono "saltare" e con
una seconda query di individuare quelli della pagina richiesta.
SELECT TOP 20 *
FROM Tabella
WHERE Chiave NOT IN (
SELECT TOP 60 Chiave
FROM Tabella
ORDER BY Chiave))
ORDER BY Chiave
La soluzione è senz'altro corretta, ma si deve prestare
attenzione al fatto che essa funziona correttamente solo se si utilizza come
criterio di ordinamento la chiave della tabella o un suo equivalente.
Lorenzo