Are SQL Server unique constraints "Silent" or do they raise an exception? - sql

I want to prevent inserting duplicate values into my tables. At first, I added code to check if the value already existed in the database, but that seems like a lot of overhead / wasted time if I can prevent it at the DDL level.
So I found this and changed one of my tables (as an example) from this:
CREATE TABLE [dbo].[ACTORS]
(
[Id] INT IDENTITY (1, 1) NOT NULL,
[ActorId] CHAR(9) NOT NULL,
[Actor] VARCHAR(50) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC),
);
to this:
CREATE TABLE [dbo].[ACTORS]
(
[Id] INT IDENTITY (1, 1) NOT NULL,
[ActorId] CHAR(9) NOT NULL,
[Actor] VARCHAR(50) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [CK_ACTORS_Column]
UNIQUE NONCLUSTERED ([ActorId] ASC)
);
I want the constraint to prevent a second identical ActorId without whin[g]ing about it. IOW, just bypass it, don't tell me about it, don't stop the app or throw an exception.
Is this how it works (silently), or will it throw an exception?

#GMB wrote in his answer "SQL Server has built-in no option (that I know about) to ignore such error".
As #a_horse_with_no_name pointed out in the comment, there is an IGNORE_DUP_KEY option of an index that is relevant here.
You said:
I want the constraint to prevent a second identical ActorId without
whin[g]ing about it. IOW, just bypass it, don't tell me about it,
don't stop the app or throw an exception.
It can be achieved with this option.
At first, I should point out that when you create a unique constraint
CONSTRAINT [CK_ACTORS_Column] UNIQUE NONCLUSTERED ([ActorId] ASC)
the engine will create a unique index to enforce the constraint. The constraint is a logical concept, the index is a physical implementation of the concept.
You can achieve the same effect by creating just an index, a unique index. When you create an index you can specify various options, including the IGNORE_DUP_KEY option.
IGNORE_DUP_KEY = { ON | OFF }
Specifies the error response when an
insert operation attempts to insert duplicate key values into a unique
index. The IGNORE_DUP_KEY option applies only to insert operations
after the index is created or rebuilt. The option has no effect when
executing CREATE INDEX, ALTER INDEX, or UPDATE. The default is OFF.
ON A warning message will occur when duplicate key values are inserted
into a unique index. Only the rows violating the uniqueness constraint
will fail.
OFF An error message will occur when duplicate key values are inserted
into a unique index. The entire INSERT operation will be rolled back.
By default this option is OFF, so an attempt to insert a duplicate key value will fail with an error. The server will roll back the INSERT operation and send this error message to your application and it will depend on your application how to handle it. If your application doesn't expect it, it will likely throw some exception.
If you set this option to ON, your application will no longer receive an error message. It will receive a warning message, which most applications usually ignore. So, it will look like the server silently ignores duplicate values and inserts only those values that are not duplicates.
It is rarely a desired behaviour to silently ignore problems, but if you really know what you are doing, you can do it.
Here is a short demo.
Let's start with your table
CREATE TABLE [dbo].[ACTORS]
(
[Id] INT IDENTITY (1, 1) NOT NULL,
[ActorId] CHAR(9) NOT NULL,
[Actor] VARCHAR(50) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC),
);
Option 1. Default. IGNORE_DUP_KEY = OFF
CREATE UNIQUE NONCLUSTERED INDEX [IX_ActorID] ON [dbo].[ACTORS]
(
[ActorId] ASC
) WITH (IGNORE_DUP_KEY = OFF)
The table is empty. Let's try to insert some values.
insert into actors (actorid, actor) values('foo', 'bar');
-- (1 row affected)
Let's try to insert a duplicate value:
insert into actors (actorid, actor) values('foo', 'baz');
--Msg 2601, Level 14, State 1, Line 4
--Cannot insert duplicate key row in object 'dbo.ACTORS' with unique index 'IX_ActorID'.
--The duplicate key value is (foo ).
--The statement has been terminated.
Let's try to insert several values in a single statement with some duplicates:
insert into actors (actorid, actor) values
('foo', 'baz'),
('fo2', 'baz'),
('fo2', 'baz'),
('fo3', 'baz'),
('fo3', 'baz'),
('fo3', 'baz'),
('fo4', 'baz');
--Msg 2601, Level 14, State 1, Line 16
--Cannot insert duplicate key row in object 'dbo.ACTORS' with unique index 'IX_ActorID'.
--The duplicate key value is (foo ).
--The statement has been terminated.
Let's see what we have in the table now.
SELECT * FROM Actors;
+----+-----------+-------+
| Id | ActorId | Actor |
+----+-----------+-------+
| 1 | foo | bar |
+----+-----------+-------+
Only the first INSERT statement succeeded and only one row was inserted.
Now, clean up.
DROP INDEX [IX_ActorID] ON [dbo].[ACTORS];
TRUNCATE TABLE dbo.Actors;
Option 2. IGNORE_DUP_KEY = ON
CREATE UNIQUE NONCLUSTERED INDEX [IX_ActorID] ON [dbo].[ACTORS]
(
[ActorId] ASC
) WITH (IGNORE_DUP_KEY = ON)
The table is empty. Let's try to insert some values.
insert into actors (actorid, actor) values('foo', 'bar');
-- (1 row affected)
Let's try to insert a duplicate value:
insert into actors (actorid, actor) values('foo', 'baz');
--Duplicate key was ignored.
--(0 rows affected)
As you can see, now it is not an error message. It is just a warning "Duplicate key was ignored."
Let's try to insert several values in a single statement with some duplicates:
insert into actors (actorid, actor) values
('foo', 'baz1'),
('fo2', 'baz2'),
('fo2', 'baz3'),
('fo3', 'baz4'),
('fo3', 'baz5'),
('fo3', 'baz6'),
('fo4', 'baz7');
--Duplicate key was ignored.
--(3 rows affected)
Here you can see that out of 7 rows only 3 rows were inserted.
Let's see what we have in the table now.
SELECT * FROM Actors;
+----+-----------+-------+
| Id | ActorId | Actor |
+----+-----------+-------+
| 1 | foo | bar |
| 4 | fo2 | baz2 |
| 6 | fo3 | baz4 |
| 9 | fo4 | baz7 |
+----+-----------+-------+
You can see that the last INSERT statement inserted non-duplicate values. Also, have a close look at the values in ID column. The IDENTITY values have gaps, because they were generated for each row that was attempted to be inserted and some of these rows were rejected by the unique index.
Overall, this index option is designed primarily for the cases when you need to insert a lot of rows in bulk, in a single INSERT statement, but you expect that some of these row may violate the unique constraint. You don't want the whole large INSERT statement to fail, you want only few violating rows to be ignored. Without this option you would have to try to insert your values one-by-one, row-by-row, which may be much slower than a single INSERT statement.

Let's try that:
insert into actors (actorid,actor) values('foo', 'bar');
-- 1 row affected
insert into actors (actorid, actor) values('foo', 'baz');
-- Msg 2627 Level 14 State 1 Line 1
-- Violation of UNIQUE KEY constraint 'CK_ACTORS_Column'.
-- Cannot insert duplicate key in object 'dbo.ACTORS'. The duplicate key value is (foo ).
A unique constraint violation does raise an error. This is how the database lets you know that something went wrong.
SQL Server has built-in no option (that I know about) to ignore such error, unlike many other databases (MySQL, Postgres, SQLite...). A workaround is to rewrite the insert with not exists and a subquery:
insert into actors (actorid, actor)
select v.*
from (values ('foo', 'bar')) v(actorid, actor)
where not exists (select 1 from actor a where a.actorid = v.actorid)
Another option is the merge statement:
merge into actors a
using (values ('foo', 'bar')) v(actorid, actor)
on v.actorid = a.actorid
when not matched then insert (actorid, actor)
values (v.actorid, v.actor)

Related

What happens if a new row is inserted when the primary key exists when using auto increment?

I am using PGsql and transferred some data from another database into my new one. The table has records starting at PK 200. The tables primary key (bigint - autoincrementing) is currently starting at 0. If I continue to insert records, eventually it will reach 200. My question is, will these records create an issue when trying to insert record 200? or will PGsql know the conflict, then find the next available AI index (say 234)?
Thanks! If it will cause a conflict, how can I set the current index of my table to the last index of data? (like 234).
My question is, will these records create an issue when trying to insert record 200?
Assuming that you have a serial column or the-like: yes, this will create an issue. The serial has no knowledge that some sequences are not available, sp this will result in a duplicate key error. Meanwhile the sequence increments also on such errors, and the next call will return the next number, and so on.
This is easily reproducible:
create table t (id serial primary key, val text);
insert into t (id, val) values (2, 'blocker');
-- 1 rows affected
insert into t (val) values ('foo');
-- 1 rows affected
insert into t (val) values ('bar');
-- ERROR: duplicate key value violates unique constraint "t_pkey"
-- DETAIL: Key (id)=(2) already exists.
insert into t (val) values ('baz');
-- 1 rows affected
select * from t order by id;
id | val
-: | :------
1 | foo
2 | blocker
3 | baz
One solution is to reset the sequence: the only safe starting point is the high watermark of the table:
select setval(
't_id_seq',
coalesce((select max(id) + 1 from t), 1),
false
);
Demo on DB Fiddlde: you can uncomment the setval() statement to see how it avoids the error.

Unique constraint on two fields, and their opposite

I have a data structure, where I have to store pairs of elements. Each pair has exactly 2 values in it, so we are employing a table, with the fields(leftvalue, rightvalue....).
These pairs should be unique, and they are considered the same, if the keys are changed.
Example: (Fruit, Apple) is the same as (Apple, Fruit).
If it is possible in an efficient way, I would put a database constraint on the fields, but not at any cost - performance is more important.
We are using MSSQL server 2008 currently, but an update is possible.
Is there an efficient way of achieving this?
Two solutions, both really about changing the problem into an easier one. I'd usually prefer the T1 solution if forcing a change on consumers is acceptable:
create table dbo.T1 (
Lft int not null,
Rgt int not null,
constraint CK_T1 CHECK (Lft < Rgt),
constraint UQ_T1 UNIQUE (Lft,Rgt)
)
go
create table dbo.T2 (
Lft int not null,
Rgt int not null
)
go
create view dbo.T2_DRI
with schemabinding
as
select
CASE WHEN Lft<Rgt THEN Lft ELSE Rgt END as Lft,
CASE WHEN Lft<Rgt THEN Rgt ELSE Lft END as Rgt
from dbo.T2
go
create unique clustered index IX_T2_DRI on dbo.T2_DRI(Lft,Rgt)
go
In both cases, neither T1 nor T2 can contain duplicate values in the Lft,Rgt pairs.
If you always store the values in order but store the direction in another column,
CREATE TABLE [Pairs]
(
[A] NVarChar(MAX) NOT NULL,
[B] NVarChar(MAX) NOT NULL,
[DirectionAB] Bit NOT NULL,
CONSTRAINT [PK_Pairs] PRIMARY KEY ([A],[B])
)
You can acheive exaclty what you want with one clustered index, and optimize your lookups too.
So when I insert the pair 'Apple', 'Fruit' I'd do,
INSERT [Pairs] VALUES ('Apple', 'Friut', 1);
Nice and easy. Then I insert 'Fruit', 'Apple',
INSERT [Pairs] VALUES ('Apple', 'Fruit', 0); -- 0 becuase order is reversed.
The insert fails because this is a primary key violation. To further illustrate, the pair 'Coconuts', 'Bananas' would be stored as
INSERT [Pairs] VALUES ('Bananas', 'Coconuts', 0);
For additional lookup performance, I'd add the index
CREATE NONCLUSTERED INDEX [IX_Pairs_Reverse] ON [Pairs] ([B], [A]);
If you can't control inserts to the table, it may be necessary to ensure that [A] and [B] are inserted correctly.
CONSTRAINT [CK_Pairs_ALessThanB] CHECK ([A] < [B])
But this may be an unnecessary performance hit, depending on how controlled your inserts are.
One way would be to create a computed column that combines the two values and put a unique constraint upon it:
create table #test (
a varchar(10) not null,
b varchar(10) not null,
both as case when a > b then a + ':' + b else b + ':' + a end persisted unique nonclustered
)
so
insert #test
select 'apple', 'fruit'
insert #test
select 'fruit', 'apple'
Gives
(1 row(s) affected)
Msg 2627, Level 14, State 1, Line 3
Violation of UNIQUE KEY constraint 'UQ__#test_____55252CB631EC6D26'. Cannot insert duplicate key in object 'dbo.#test'.
The statement has been terminated.
Unique constraint on two/more fields is possible but on their opposite no...
SQL Server 2005 Unique constraint on two columns
Unique constraint on multiple columns
How do I apply unique constraint on two columns SQL Server?
http://www.w3schools.com/sql/sql_unique.asp

Add unique constraint to combination of two columns

I have a table and, somehow, the same person got into my Person table twice. Right now, the primary key is just an autonumber but there are two other fields that exist that I want to force to be unique.
For example, the fields are:
ID
Name
Active
PersonNumber
I only want 1 record with a unique PersonNumber and Active = 1.
(So the combination of the two fields needs to be unique)
What is the best way on an existing table in SQL server I can make it so if anyone else does an insert with the same value as an existing value, it fails so I don't have to worry about this in my application code.
Once you have removed your duplicate(s):
ALTER TABLE dbo.yourtablename
ADD CONSTRAINT uq_yourtablename UNIQUE(column1, column2);
or
CREATE UNIQUE INDEX uq_yourtablename
ON dbo.yourtablename(column1, column2);
Of course, it can often be better to check for this violation first, before just letting SQL Server try to insert the row and returning an exception (exceptions are expensive).
Performance impact of different error handling techniques
Checking for potential constraint violations before entering TRY/CATCH
If you want to prevent exceptions from bubbling up to the application, without making changes to the application, you can use an INSTEAD OF trigger:
CREATE TRIGGER dbo.BlockDuplicatesYourTable
ON dbo.YourTable
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM inserted AS i
INNER JOIN dbo.YourTable AS t
ON i.column1 = t.column1
AND i.column2 = t.column2
)
BEGIN
INSERT dbo.YourTable(column1, column2, ...)
SELECT column1, column2, ... FROM inserted;
END
ELSE
BEGIN
PRINT 'Did nothing.';
END
END
GO
But if you don't tell the user they didn't perform the insert, they're going to wonder why the data isn't there and no exception was reported.
EDIT here is an example that does exactly what you're asking for, even using the same names as your question, and proves it. You should try it out before assuming the above ideas only treat one column or the other as opposed to the combination...
USE tempdb;
GO
CREATE TABLE dbo.Person
(
ID INT IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(32),
Active BIT,
PersonNumber INT
);
GO
ALTER TABLE dbo.Person
ADD CONSTRAINT uq_Person UNIQUE(PersonNumber, Active);
GO
-- succeeds:
INSERT dbo.Person(Name, Active, PersonNumber)
VALUES(N'foo', 1, 22);
GO
-- succeeds:
INSERT dbo.Person(Name, Active, PersonNumber)
VALUES(N'foo', 0, 22);
GO
-- fails:
INSERT dbo.Person(Name, Active, PersonNumber)
VALUES(N'foo', 1, 22);
GO
Data in the table after all of this:
ID Name Active PersonNumber
---- ------ ------ ------------
1 foo 1 22
2 foo 0 22
Error message on the last insert:
Msg 2627, Level 14, State 1, Line 3
Violation of UNIQUE KEY constraint 'uq_Person'. Cannot insert duplicate key in object 'dbo.Person'.
The statement has been terminated.
Also I blogged more recently about a solution to applying a unique constraint to two columns in either order:
Enforce a Unique Constraint Where Order Does Not Matter
This can also be done in the GUI:
Under the table "Person", right click Indexes
Click/hover New Index
Click Non-Clustered Index...
A default Index name will be given but you may want to change it.
Check Unique checkbox
Click Add... button
Check the columns you want included
Click OK in each window.
In my case, I needed to allow many inactives and only one combination of two keys active, like this:
UUL_USR_IDF UUL_UND_IDF UUL_ATUAL
137 18 0
137 19 0
137 20 1
137 21 0
This seems to work:
CREATE UNIQUE NONCLUSTERED INDEX UQ_USR_UND_UUL_USR_IDF_UUL_ATUAL
ON USER_UND(UUL_USR_IDF, UUL_ATUAL)
WHERE UUL_ATUAL = 1;
Here are my test cases:
SELECT * FROM USER_UND WHERE UUL_USR_IDF = 137
insert into USER_UND values (137, 22, 1) --I CAN NOT => Cannot insert duplicate key row in object 'dbo.USER_UND' with unique index 'UQ_USR_UND_UUL_USR_IDF_UUL_ATUAL'. The duplicate key value is (137, 1).
insert into USER_UND values (137, 23, 0) --I CAN
insert into USER_UND values (137, 24, 0) --I CAN
DELETE FROM USER_UND WHERE UUL_USR_ID = 137
insert into USER_UND values (137, 22, 1) --I CAN
insert into USER_UND values (137, 27, 1) --I CAN NOT => Cannot insert duplicate key row in object 'dbo.USER_UND' with unique index 'UQ_USR_UND_UUL_USR_IDF_UUL_ATUAL'. The duplicate key value is (137, 1).
insert into USER_UND values (137, 28, 0) --I CAN
insert into USER_UND values (137, 29, 0) --I CAN
And if you have lot insert queries but not wanna ger a ERROR message everytime , you can do it:
CREATE UNIQUE NONCLUSTERED INDEX SK01 ON dbo.Person(ID,Name,Active,PersonNumber)
WITH(IGNORE_DUP_KEY = ON)

SQL can I have a "conditionally unique" constraint on a table?

I've had this come up a couple times in my career, and none of my local peers seems to be able to answer it. Say I have a table that has a "Description" field which is a candidate key, except that sometimes a user will stop halfway through the process. So for maybe 25% of the records this value is null, but for all that are not NULL, it must be unique.
Another example might be a table which must maintain multiple "versions" of a record, and a bit value indicates which one is the "active" one. So the "candidate key" is always populated, but there may be three versions that are identical (with 0 in the active bit) and only one that is active (1 in the active bit).
I have alternate methods to solve these problems (in the first case, enforce the rule code, either in the stored procedure or business layer, and in the second, populate an archive table with a trigger and UNION the tables when I need a history). I don't want alternatives (unless there are demonstrably better solutions), I'm just wondering if any flavor of SQL can express "conditional uniqueness" in this way. I'm using MS SQL, so if there's a way to do it in that, great. I'm mostly just academically interested in the problem.
If you are using SQL Server 2008 a Index filter would maybe your solution:
http://msdn.microsoft.com/en-us/library/ms188783.aspx
This is how I enforce a Unique Index with multiple NULL values
CREATE UNIQUE INDEX [IDX_Blah] ON [tblBlah] ([MyCol]) WHERE [MyCol] IS NOT NULL
In the case of descriptions which are not yet completed, I wouldn't have those in the same table as the finalized descriptions. The final table would then have a unique index or primary key on the description.
In the case of the active/inactive, again I might have separate tables as you did with an "archive" or "history" table, but another possible way to do it in MS SQL Server at least is through the use of an indexed view:
CREATE TABLE Test_Conditionally_Unique
(
my_id INT NOT NULL,
active BIT NOT NULL DEFAULT 0
)
GO
CREATE VIEW dbo.Test_Conditionally_Unique_View
WITH SCHEMABINDING
AS
SELECT
my_id
FROM
dbo.Test_Conditionally_Unique
WHERE
active = 1
GO
CREATE UNIQUE CLUSTERED INDEX IDX1 ON Test_Conditionally_Unique_View (my_id)
GO
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 1)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 1)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 1) -- This insert will fail
You could use this same method for the NULL/Valued descriptions as well.
Thanks for the comments, the initial version of this answer was wrong.
Here's a trick using a computed column that effectively allows a nullable unique constraint in SQL Server:
create table NullAndUnique
(
id int identity,
name varchar(50),
uniqueName as case
when name is null then cast(id as varchar(51))
else name + '_' end,
unique(uniqueName)
)
insert into NullAndUnique default values
insert into NullAndUnique default values -- Works
insert into NullAndUnique default values -- not accidentally :)
insert into NullAndUnique (name) values ('Joel')
insert into NullAndUnique (name) values ('Joel') -- Boom!
It basically uses the id when the name is null. The + '_' is to avoid cases where name might be numeric, like 1, which could collide with the id.
I'm not entirely aware of your intended use or your tables, but you could try using a one to one relationship. Split out this "sometimes" unique column into a new table, create the UNIQUE index on that column in the new table and FK back to the original table using the original tables PK. Only have a row in this new table when the "unique" data is supposed to exist.
OLD tables:
TableA
ID pk
Col1 sometimes unique
Col...
NEW tables:
TableA
ID
Col...
TableB
ID PK, FK to TableA.ID
Col1 unique index
Oracle does. A fully null key is not indexed by a Btree in index in Oracle, and Oracle uses Btree indexes to enforce unique constraints.
Assuming one wished to version ID_COLUMN based on the ACTIVE_FLAG being set to 1:
CREATE UNIQUE INDEX idx_versioning_id ON mytable
(CASE active_flag WHEN 0 THEN NULL ELSE active_flag END,
CASE active_flag WHEN 0 THEN NULL ELSE id_column END);

conditional unique constraint

I have a situation where i need to enforce a unique constraint on a set of columns, but only for one value of a column.
So for example I have a table like Table(ID, Name, RecordStatus).
RecordStatus can only have a value 1 or 2 (active or deleted), and I want to create a unique constraint on (ID, RecordStatus) only when RecordStatus = 1, since I don't care if there are multiple deleted records with the same ID.
Apart from writing triggers, can I do that?
I am using SQL Server 2005.
Behold, the filtered index. From the documentation (emphasis mine):
A filtered index is an optimized nonclustered index especially suited to cover queries that select from a well-defined subset of data. It uses a filter predicate to index a portion of rows in the table. A well-designed filtered index can improve query performance as well as reduce index maintenance and storage costs compared with full-table indexes.
And here's an example combining a unique index with a filter predicate:
create unique index MyIndex
on MyTable(ID)
where RecordStatus = 1;
This essentially enforces uniqueness of ID when RecordStatus is 1.
Following the creation of that index, a uniqueness violation will raise an arror:
Msg 2601, Level 14, State 1, Line 13
Cannot insert duplicate key row in object 'dbo.MyTable' with unique index 'MyIndex'. The duplicate key value is (9999).
Note: the filtered index was introduced in SQL Server 2008. For earlier versions of SQL Server, please see this answer.
Add a check constraint like this. The difference is, you'll return false if Status = 1 and Count > 0.
http://msdn.microsoft.com/en-us/library/ms188258.aspx
CREATE TABLE CheckConstraint
(
Id TINYINT,
Name VARCHAR(50),
RecordStatus TINYINT
)
GO
CREATE FUNCTION CheckActiveCount(
#Id INT
) RETURNS INT AS BEGIN
DECLARE #ret INT;
SELECT #ret = COUNT(*) FROM CheckConstraint WHERE Id = #Id AND RecordStatus = 1;
RETURN #ret;
END;
GO
ALTER TABLE CheckConstraint
ADD CONSTRAINT CheckActiveCountConstraint CHECK (NOT (dbo.CheckActiveCount(Id) > 1 AND RecordStatus = 1));
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 1);
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 2);
-- Msg 547, Level 16, State 0, Line 14
-- The INSERT statement conflicted with the CHECK constraint "CheckActiveCountConstraint". The conflict occurred in database "TestSchema", table "dbo.CheckConstraint".
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);
SELECT * FROM CheckConstraint;
-- Id Name RecordStatus
-- ---- ------------ ------------
-- 1 No Problems 2
-- 1 No Problems 2
-- 1 No Problems 2
-- 1 No Problems 1
-- 2 Oh no! 1
-- 2 Oh no! 2
ALTER TABLE CheckConstraint
DROP CONSTRAINT CheckActiveCountConstraint;
DROP FUNCTION CheckActiveCount;
DROP TABLE CheckConstraint;
You could move the deleted records to a table that lacks the constraint, and perhaps use a view with UNION of the two tables to preserve the appearance of a single table.
You can do this in a really hacky way...
Create an schemabound view on your table.
CREATE VIEW Whatever
SELECT * FROM Table
WHERE RecordStatus = 1
Now create a unique constraint on the view with the fields you want.
One note about schemabound views though, if you change the underlying tables you will have to recreate the view. Plenty of gotchas because of that.
For those still searching for a solution, I came accross a nice answer, to a similar question and I think this can be still useful for many. While moving deleted records to another table may be a better solution, for those who don't want to move the record can use the idea in the linked answer which is as follows.
Set deleted=0 when the record is available/active.
Set deleted=<row_id or some other unique value> when marking the row
as deleted.
If you can't use NULL as a RecordStatus as Bill's suggested, you could combine his idea with a function-based index. Create a function that returns NULL if the RecordStatus is not one of the values you want to consider in your constraint (and the RecordStatus otherwise) and create an index over that.
That'll have the advantage that you don't have to explicitly examine other rows in the table in your constraint, which could cause you performance issues.
I should say I don't know SQL server at all, but I have successfully used this approach in Oracle.
Because, you are going to allow duplicates, a unique constraint will not work. You can create a check constraint for RecordStatus column and a stored procedure for INSERT that checks the existing active records before inserting duplicate IDs.