CREATE TYPE2 UNIQUE WHERE NULL INDEX
This pops up every so often, as it does Here. The post title is the DB2 syntax for achieving a unique index that allows for nulls. I always felt that since Nulls are not equal to anything, not even themselves, then how could you get a dup key violation. To be fair, even in DB2 you can not have a primary key that allows more than 1 Null. But you can create a unique index in lieu of the the PK that will allow nulls. This allows the ability to build non-identifying relationships, something I believe is lacking in SQL Server.
In any event, there are all kinds of work arounds to do this, as David shows here. Also, with my exposure to SQL Server, it is the general concensus to avoid nulls at all cost. I was raised differently. After reading Bonnie Bakers article, “Much Ado about Nulls” I am convinved more now than ever (well, ok, that was about 5 years ago).
Anyway, here's a method using a trigger where you can simulate this.
Update: I forgot to add code to handle the update...duh. The additional code is added in Purple for anyone who has already downloaded the code. Or you can just cut and paste it all over.
Note: With the new code added for the update, it's kind of a hack, since I'm using the identity column. What I should really do, instead of using the surrogate, is to perform a logical update to the natural keys(Col1 and Col2) by performing a DELETE and a new INSERT operation. This eliminates the dependancy on the surrogate. But as an example, it serves it's purpose here. Thanks for reading.
USE Northwind
GO
SET NOCOUNT ON
CREATE TABLE myTable99 (
[id] int IDENTITY (1, 1) NOT NULL ,
Col1 varchar (15) NULL ,
Col2 varchar (14) NULL ,
)
CREATE INDEX myTable99_Col1 ON myTable99(Col1)
CREATE INDEX myTable99_Col2 ON myTable99(Col2)
GO
CREATE TRIGGER myTrigger99 ON myTable99
INSTEAD OF INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
IF EXISTS ( SELECT *
FROM myTable99 t
INNER JOIN inserted i
ON t.Col1 = i.Col1
AND t.Col2 = i.Col2
WHERE i.Col1 IS NOT NULL
AND i.Col2 IS NOT NULL)
OR EXISTS ( SELECT *
FROM myTable99 t
INNER JOIN inserted i
ON t.Col1 = i.Col1
WHERE i.Col1 IS NOT NULL
AND i.Col2 IS NULL AND t.Col2 IS NULL)
OR EXISTS ( SELECT *
FROM myTable99 t
INNER JOIN inserted i
ON t.Col2 = i.Col2
WHERE i.Col1 IS NULL AND t.Col1 IS NULL
AND i.Col2 IS NOT NULL)
BEGIN
RAISERROR ('Violation of Logical PRIMARY KEY constraint. Cannot insert duplicate key in object myTest99', 16, 1)
ROLLBACK TRANSACTION
END
ELSE IF EXISTS(SELECT * FROM inserted) AND NOT EXISTS (SELECT * FROM deleted)
INSERT INTO myTable99(Col1, Col2) SELECT Col1, Col2 FROM inserted
ELSE
UPDATE t
SET Col1=i.Col1
, Col2=i.Col2
FROM myTable99 t INNER JOIN inserted i
ON t.[id] = i.[id]
SET NOCOUNT OFF
END
GO
INSERT INTO myTable99(Col1, Col2)
SELECT '1' , Null UNION ALL
SELECT '1' , '1' UNION ALL
SELECT Null, '1' UNION ALL
SELECT '2' , '2' UNION ALL
SELECT '2' , Null UNION ALL
SELECT Null, '2' UNION ALL
SELECT Null, '3' UNION ALL
SELECT '3' , Null UNION ALL
SELECT '3' , '3'
GO
SELECT * FROM myTable99
GO
UPDATE myTable99 SET Col1 = 4 WHERE id = 9
UPDATE myTable99 SET Col2 = 4 WHERE id = 9
SELECT * FROM myTable99
GO
--Dup Keys
INSERT INTO myTable99(Col1, Col2)
SELECT '1', Null
GO
INSERT INTO myTable99(Col1, Col2)
SELECT '1', '1'
GO
INSERT INTO myTable99(Col1, Col2)
SELECT Null, '1'
GO
UPDATE myTable99 SET Col2 = 4 WHERE id = 9
GO
SET NOCOUNT OFF
DROP TRIGGER myTrigger99
DROP TABLE myTable99
GO
Legacy Comments
Adam Machanic
2005-04-21 |
re: CREATE TYPE2 UNIQUE WHERE NULL INDEX I think a slightly more elegant way to handle this is via an indexed view. Create the following indexed view against the table in order to enforce it: CREATE VIEW un_myTable99 WITH SCHEMABINDING AS SELECT Col1, Col2 FROM dbo.myTable99 WHERE Col1 IS NOT NULL OR Col2 IS NOT NULL GO CREATE UNIQUE CLUSTERED INDEX IX_un_myTable99 ON un_myTable99(Col1, Col2) GO |
rnickel
2005-08-23 |
re: CREATE TYPE2 UNIQUE WHERE NULL INDEX I love your approach, but there's a much simpler way to write the trigger that overcomes some of your shortcomings here: 1) You've got to repeat your basic test three times, which is already a lot, but the number would increase geometrically if you wanted more columns in the logical key. 2) The trigger (especially the UPDATE part) will break if columns are added or dropped from the table This seems simpler to me: CREATE TRIGGER myTrigger99 ON myTable99 FOR INSERT, UPDATE AS IF EXISTS ( SELECT 1 FROM myTable99 a INNER JOIN inserted i ON a.Col1 = i.Col1 AND a.Col2 = i.Col2 -- nulls will drop out; they don't equal anything GROUP BY a.Col1, a.Col2 HAVING COUNT(*) > 1 -- if there is more than one row with given values, uniqueness has been violated ) BEGIN RAISERROR ('Violation of Logical PRIMARY KEY constraint. Cannot insert duplicate key in object myTest99', 16, 1) ROLLBACK TRANSACTION END GO |
Trevor
2005-09-28 |
re: CREATE TYPE2 UNIQUE WHERE NULL INDEX That was really handy rnickel - thanks! |