Add unique constraint to combination of two columns - sql

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)

Related

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

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)

SQL unique constraint on either of 2 columns

I have a table in SQL that I would like to have a unique constraint so that any of two values cannot already exist.
for example if I have 2 columns, I would like it to not insert if the value in column B does not exist in column A or column B.
Is this possible and if so how is it done?
example:
Column A | Column B
--------------------
4 | 6
I would want any object that tries to insert 4 or 6 not to be allowed into the table
Trigger with ROLLBACK TRANSACTION is the way to go.
create trigger dbo.something after insert as
begin
if exists ( select * from inserted where ...check here if your data already exists... )
begin
rollback transaction
raiserror ('some message', 16, 1)
end
end
You can create a function which takes in these values & create a check constraint on it (referencing your functions return values) to your table.
create table t11 (Code int, code2 int)
create function fnCheckValues (#Val1 int, #Val2 int)
Returns int /*YOu can write a better implementation*/
as
Begin
DECLARE #CntRow int
IF(#Val1 IS NULL OR #Val2 IS NULL) RETURN 0
select #CntRow = count(*) from t11
where Code in (#Val1,#Val2 ) or Code2 in (#Val1,#Val2 )
RETURN #CntRow
End
GO
alter table t11 Add constraint CK_123 check ([dbo].[fnCheckValues]([Code],[code2])<=(1))
When one want to enforce a multi-row constraint that is not offered by the database engine, the obvious solution is use of a trigger or stored procedure. This often does not work because the database isolates the transactions the triggers and stored procedures run in, allowing violations in the presense of concurrency.
Instead turn the constraint into something that the database engine will enforce.
CREATE TABLE dbo.T (A INT, B INT)
GO
CREATE TABLE dbo.T_Constraint_Helper (ColumnName sysname PRIMARY KEY)
INSERT INTO dbo.T_Constraint_Helper (ColumnName)
VALUES ('A'), ('B')
GO
CREATE VIEW T_Constraint_VW
WITH SCHEMABINDING AS
SELECT CASE CH.ColumnName WHEN 'A' THEN T.A ELSE T.B END AS Value
FROM dbo.T
CROSS JOIN dbo.T_Constraint_Helper CH
GO
CREATE UNIQUE CLUSTERED INDEX FunnyConstraint_VW_UK ON dbo.T_Constraint_VW (Value)
GO
INSERT INTO T VALUES (1, 2)
-- works
INSERT INTO T VALUES (2, 3)
-- Msg 2601, Level 14, State 1, Line 1
-- Cannot insert duplicate key row in object 'dbo.T_Constraint_VW' with unique index 'T_Constraint_VW_UK'. The duplicate key value is (2).
INSERT INTO T VALUES (4, 4)
-- Msg 2601, Level 14, State 1, Line 1
-- Cannot insert duplicate key row in object 'dbo.T_Constraint_VW' with unique index 'T_Constraint_VW_UK'. The duplicate key value is (4).
INSERT INTO T VALUES (5, 6)
-- works

How not to insert specific value into database

I have MS SQL Server database and insert some values to one of the tables.
Let's say that the table contains columns ID, int Status and text Text.
If possible, I would like to create a trigger which prevents from writing specific incorrect status (say 1) to the table. Instead, 0 should be written. However, other columns should be preserved when inserting new values:
If the new row written is (1, 4, "some text"), it is written as is.
If the new row written is (1, 1, "another text"), it is written as (1, 0, "another text")
Is it possible to create such trigger? How?
EDIT: I need to allow writing such record even if status column is invalid, so foreign keys will not work for me.
I think you would need a foreign key to ensure data integrity even if you choose to use a trigger (though I would myself prefer a 'helper' stored proc -- triggers can cause debugging hell) e.g.
CREATE TABLE MyStuff
(
ID INTEGER NOT NULL UNIQUE,
Status INTEGER NOT NULL
CHECK (Status IN (0, 1)),
UNIQUE (Status, ID)
);
CREATE TABLE MyZeroStuff
(
ID INTEGER NOT NULL,
Status INTEGER NOT NULL
CHECK (Status = 0),
FOREIGN KEY (Status, ID)
REFERENCES MyStuff (Status, ID),
my_text VARCHAR(20) NOT NULL
);
CREATE TRIGGER tr__MyZeroStuff
ON MyZeroStuff
INSTEAD OF INSERT, UPDATE
AS
BEGIN;
INSERT INTO MyZeroStuff (ID, Status, my_text)
SELECT i.ID, 0, i.my_text
FROM inserted AS i;
END;
An insert trigger has been mentioned, but another way to achieve this is to have a foriegn key on your Status column which points back to a Status table - this will not allow the write and change the value, instead it will simply disallow the write if the foriegn key is not valid.
Check out referential integrity for more info on this option.

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.