DBMS: MS Sql Server 2005, Standard
I’d like to make a table constraint to have only one record have a particular value within a subset of the table (where the rows share a value in a particular column). Is this possible?
Example:
I have records in myTable which have a non-unique foreign key (fk1), and a bit column called isPrimary to mark out that this particular one should be used by our app for special logic.
in the abstract, it looks like this:
myTable
-------------
pk1 (int, not null)
name (varchar(50), null)
fk1 (int, not null)
isPrimary (bit, not null)
I want to ensure that there is one and only one record with the isPrimary flag set to 1, for each unique value of fk1.
Data example:
This should be legal:
pk1 name fk1 isPrimary
---- ----- ----- ----------
1 Bill 111 1
2 Tom 111 0
3 Dick 222 1
4 Harry 222 0
But this should not be (more than one where fk=111):
pk1 name fk1 isPrimary
---- ----- ----- ----------
1 Bill 111 1
2 Tom 111 1
3 Dick 222 1
4 Harry 222 0
And neither should this (none where fk=222):
pk1 name fk1 isPrimary
---- ----- ----- ----------
1 Bill 111 1
2 Tom 111 0
3 Dick 222 0
4 Harry 222 0
Is there a way to do this with a table constraint?
UPDATE
I’ve gone with Martin Smith’s answer for now, though I’ll be pushing for JohnFx’s refactor in an upcoming release, as it’s the best long-term solution. However I wanted to post my updated UDF based on Raze2dust’s answer, in case future readers decide that is a better fit for their needs.
CREATE FUNCTION [dbo].[OneIsPrimaryPerFK1](@fk1 INT, @dummyIsPrimary BIT)
RETURNS INT
AS
BEGIN
DECLARE @retval INT;
DECLARE @primarySum INT;
SET @retval = 0;
DECLARE @TempTable TABLE (
fk1 INT,
PrimarySum INT)
INSERT INTO @TempTable
SELECT fk1, SUM(CAST(isPrimary AS INT)) AS PrimarySum
FROM FacAdmin
WHERE fk1 = @fk1
GROUP BY fk1;
SELECT @primarySum = PrimarySum FROM @TempTable;
IF(@primarySum=1)
BEGIN
SET @retval = 1
END
RETURN @retval
END;
Changes:
- Used @tempTable rather than
tempTable (in memory v. written to disk) as required by udf
- passed @fk1 as a parameter so I can select for uniqueness within one
group of fk1 values. - tricky had to also pass isPrimary even though it isn’t
necessary for the logic of the
function, otherwise the SQL2005
optimizer will not run the check
constraint when isPrimary is
updated.
Using UDFs in check constraints can fail under snapshot isolation or multirow updates.
Assuming that all your fk1 and pk1 values are currently (and will always be) positive you could create a computed column with the following definition
then add a unique constraint to that. Or if that assumption can’t be made then maybe