CHECK CONSTRAINT

I constraint rafforzano l’integrità del database e definiscono regole relativamente ai valori ammessi in una o più colonne di una tabella. Un constraint di tipo CHECK rafforza l’integrità di dominio limitando i valori che possono essere memorizzati in una colonna.

Mi è recentemente successo di dover implementare un vincolo CHECK per controllare l’univocità dei valori di un campo in una situazione in cui non era possibile applicare un indice UNIQUE. Avrei potuto implementare un trigger ma dove possibile l’utilizzo di un CHECK constraint è da preferire all’utilizzo di un trigger.

Ipotizziamo questo scenario:

Disponiamo di un database SQL Server 2005 in cui sono presenti le tabelle anagrafiche dbo.Magazzini e dbo.Vani utilizzate rispettivamente per memorizzare l’anagrafica dei magazzini aziendali ed la relativa suddivisione in vani. Ogni magazzino deve avere (obbligatoriamente) almeno un vano che chiameremo vano a terra (o vano di default) creato al momento della creazione del magazzino stesso. Il codice del vano a terra è rappresentato dalla stringa fissa ‘VTERRA’.

Le tabelle dbo.Magazzini e dbo.Vani hanno la seguente struttura: 

dbo.Magazzini:

-- Drop table dbo.Magazzini if exists

IF (OBJECT_ID('Magazzini', 'U') IS NOT NULL) 

  DROP TABLE dbo.Magazzini

GO 

-- Create table dbo.Magazzini

CREATE TABLE [dbo].[Magazzini](ID INT IDENTITY(1, 1) NOT NULL, CODICE CHAR(4) NOT NULL,  DESCRIZIONE CHAR(80) NOT NULL  PRIMARY KEY (ID))

dbo.Vani:

 -- Drop table dbo.Vani if exists

IF (OBJECT_ID('Vani', 'U') IS NOT NULL)

  DROP TABLE dbo.VaniGO

-- Create table dbo.Vani

CREATE TABLE [dbo].[Vani](ID INT IDENTITY(1, 1) NOT NULL, CODICE CHAR(6) NOT NULL, DESCRIZIONE CHAR(80) NOT NULL,  IDMAGAZZINI INT NOT NULL PRIMARY KEY (ID))

-- Foreign Key

ALTER TABLE [dbo].[Vani] WITH NOCHECK ADD CONSTRAINT [FK__Magazzini_ID] FOREIGN KEY (IDMAGAZZINI) REFERENCES [dbo].[Magazzini] (ID)

Le due tabelle anagrafiche contengono i seguenti dati di prova:

-- Inserimento dati di prova

INSERT INTO [dbo].[Magazzini] (CODICE, DESCRIZIONE) VALUES ('MATP', 'Materie Prime')

INSERT INTO [dbo].[Vani] (CODICE, DESCRIZIONE, IDMAGAZZINI) Select 'VTERRA', 'Vano a Terra', (Select Id From Magazzini Where Codice='MATP')

INSERT INTO [dbo].[Vani] (CODICE, DESCRIZIONE, IDMAGAZZINI) Select 'CAS001', 'Cassetto 001', (Select Id From Magazzini Where Codice='MATP')

INSERT INTO [dbo].[Vani] (CODICE, DESCRIZIONE, IDMAGAZZINI) Select 'CAS002', 'Cassetto 002', (Select Id From Magazzini Where Codice='MATP')

INSERT INTO [dbo].[Magazzini] (CODICE, DESCRIZIONE) VALUES ('SMLA', 'Semilavorati')

INSERT INTO [dbo].[Vani] (CODICE, DESCRIZIONE, IDMAGAZZINI) Select 'VTERRA', 'Vano a Terra', (Select Id From Magazzini Where Codice='SMLA')

Ipotizziamo di dover implementare un vincolo che garantisca l’univocità del campo codice vano impedendo la creazione di due vani con lo stesso codice (a prescindere dal magazzino di appartenenza).

Il vincolo dovrà però tenere conto dei vani a terra (o vani di default) che vogliamo mantenere codificati con un codice fisso per tutti i vani a terra di tutti i magazzini. Non è quindi possibile implementare un constraint di tipo UNIQUE perché impedirebbe la creazione di nuovi magazzini e dei relativi vani a terra.

Scegliamo quindi d’implementare un CHECK constraint che sfrutta la funzione dbo.UDF_UNIQUE_COD_VANO() per verificare i valori che possono essere memorizzati nella colonna codice. La funzione dbo.UDF_UNIQUE_COD_VANO() ed il vincolo CHECK sono definiti in questo modo:

-- Drop function if exists

IF (OBJECT_ID('UDF_UNIQUE_COD_VANO', 'FN') IS NOT NULL)

  DROP FUNCTION dbo.UDF_UNIQUE_COD_VANO

GO

-- Create function

CREATE FUNCTION dbo.UDF_UNIQUE_COD_VANO(@CODVANO AS CHAR(6)) RETURNS INT

AS

BEGIN

  DECLARE @EXISTS INT

  SET @EXISTS = 0

  IF (@CODVANO <> 'VTERRA')

  BEGIN

    SELECT

      @EXISTS=(CASE WHEN CODICE IS NOT NULL THEN -1 ELSE 0 END)

    FROM dbo.VANI

    WHERE (CODICE = @CODVANO)

  END

  RETURN(@EXISTS)

END

-- Create CHECK CONSTRAINT 

 ALTER TABLE dbo.VANI WITH NOCHECK ADD CONSTRAINT CHK_CODICE_VANO CHECK (dbo.UDF_UNIQUE_COD_VANO(CODICE)=0)

Proviamo ora a codificare un nuovo vano nel magazzino "Semilavorati", si tratta del “Cassetto 003” il cui codice “CAS003” non esiste nella tabella dbo.Vani. Il seguente comando di INSERT non potrà essere eseguito per la limitazione introdotta dal vincolo CHECK. Il vano che stiamo tentando di inserire non è effettivamente presente, ma il CHECK constraint blocca comunque l’inserimento.

INSERT INTO [dbo].[Vani] (CODICE, DESCRIZIONE, IDMAGAZZINI)

Select 'CAS003', 'Cassetto 003', (Select Id From Magazzini Where Codice='SMLA')

Msg 547, Level 16, State 0, Line 1The INSERT statement conflicted with the CHECK constraint "CHK_CODICE_VANO". The conflict occurred in database "TestDB", table "dbo.Vani", column 'CODICE'.The statement has been terminated.

 

Cosa sta succedendo ? Ho investito un po’ di tempo per verificare la situazione, analizziamo la sequenza degli eventi e delle attività eseguite da SQL Server durante l’operazione di INSERT. La figura seguente illustra la traccia SQL Profiler:

click to enlarge - SQL Profiler

 Il vincolo CHECK, attraverso la funzione dbo.UDF_UNIQUE_COD_VANO(), verifica l’esistenza del vano dopo l’evento SQL:BatchStarting relativo al comando di INSERT.

Il record non ancora committato verrà quindi estratto dalla funzione dbo.UDF_UNIQUE_COD_VANO() che restituirà al chiamante (CHECK) il valore –1 per indicare che il codice vano passato come argomento risulta essere già presente. L’espressione booleana valutata nel vincolo CHECK farà quindi fallire l’operazione di INSERT.

La funzione dbo.UDF_UNIQUE_COD_VANO() dovrà quindi essere riscritta in questo modo:

-- Create function

CREATE FUNCTION dbo.UDF_UNIQUE_COD_VANO(@CODVANO AS CHAR(6)) RETURNS INT

AS

BEGIN

  DECLARE @EXISTS INT

  IF (@CODVANO <> 'VTERRA')

    SELECT

      @EXISTS = ISNULL(COUNT(CODICE), 0)

    FROM

      dbo.VANI

    WHERE (CODICE = @CODVANO)

  ELSE

    SET @EXISTS = 0

  IF (@EXISTS >= 2)

    SET @EXISTS = -1

  ELSE

    SET @EXISTS = 0

  RETURN @EXISTS

END

La codifica del vano “Cassetto 003” con codice “CAS003”, sul magazzino "Semilavorati", potrà ora essere eseguita correttamente.

INSERT INTO [dbo].[Vani] (CODICE, DESCRIZIONE, IDMAGAZZINI)

Select 'CAS003', 'Cassetto 003', (Select Id From Magazzini Where Codice='SMLA')

 

Published domenica 13 luglio 2008 18.42 by sgovoni

Comments

No Comments