views:

300

answers:

5

I have a table with these fields:

User_id, User_type, User_address

Is it possible to add a constraint where only one record can exist where user_type = 'xyz' per user_id? There can be as many user_type = 'abc' as we wish but only one 'xyz'.

I know that this is not the greatest design but it is what is there currently and I need to lock it down a bit.

Thanks

+3  A: 

you'll need to use a trigger...

CREATE TRIGGER yourTriggerName ON YourTableName
AFTER INSERT,UPDATE
AS

IF EXISTS (SELECT
               y.User_id --,COUNT(y.User_Type)
               FROM YourTableName       y 
                   INNER JOIN inserted  i ON y.User_id=i.User_id
               WHERE y.User_Type='xyz'
               GROUP BY y.User_id 
               HAVING COUNT(y.User_Type)>1
          )
BEGIN

    ROLLBACK

END
go

also, make sure there is an index on User_id+User_type

KM
I decided to go with the trigger. One of the reasons I selected the trigger is the option to have additional code when this condition tries to occur. Also there isn't really a need for performance tweaking and I don't see a need to switch to snapshot isolation so the setup for the index isn't necessary at this time. If the need for this table changes, redesigning the schema is more likely and this trigger will no longer be needed. Thank you all for your time and answers.
SDC
You need to explicitly set isolation level in your trigger, otherwise switching to snapshot isolation will break it.
AlexKuznetsov
A: 

I'm not sure its the best way but you could always create a insert/update trigger

Gratzy
A: 

You can use a check constraint to enforce this rule.

CREATE FUNCTION CheckUserTypeXyzExistAtMostOnce()
RETURNS bit
AS 
BEGIN
   DECLARE @count int
   SELECT @count = COUNT(*) FROM dbo.MyTable WHERE UserType = 'xyz'
   RETURN @count <= 1
END;

ALTER TABLE dbo.MyTable
ADD CONSTRAINT UserTypeConstraint CHECK (dbo.CheckUserTypeXyzExistAtMostOnce());
Daniel Brückner
if you insert a set of rows, this will run for each row in the set
KM
+1  A: 

I had the same idea as Daniel, but I think your constraint as you put it needs to check for at most 1 XYZ type PER USER:

CREATE FUNCTION CheckUserTypeXyzExistAtMostOnce(@User_id int)
RETURNS bit
AS
BEGIN
    DECLARE @count int
    SELECT @count = COUNT(*) FROM dbo.MyTable WHERE User_id = @User_id AND User_type = 'xyz'
    RETURN @count <= 1
END;
ALTER TABLE dbo.MyTableADD CONSTRAINT UserTypeConstraint CHECK (dbo.CheckUserTypeXyzExistAtMostOnce(User_id));
Matt Hamsmith
good catch on the one "xyz" _per user_, I had been thinking one "xyz" per the entire table
KM
wrapping a UDF in a check constraint may not work if you modify more than one row at a time or if you use snapshot isolation:
AlexKuznetsov
+3  A: 

A very common question. My canned answer:

Use Computed Columns to Implement Complex Business Rules

You can also use an indexed view to accomplish the same. Note that wrapping a UDF in a check constraint may not work if you modify more than one row at a time or if you use snapshot isolation:

Scalar UDFs wrapped in CHECK constraints are very slow and may fail for multirow updates

Why am I recommending an index, not a trigger? Because if I have an index I am 100% sure all my data is clean. With triggers, it is not the case. Sometimes triggers do not fire, sometimes they have bugs. Another trigger can override this one.

AlexKuznetsov
(+1) Good article. I did not know the performance was so horrible.
Matt Hamsmith
A bit convoluted, but it would work and not require table scans for ever data change. And the index would help with performance.
Philip Kelley
What would be the advantage of a computed column used in an view be over a trigger (like the one in my answer)? I actually try to avoid triggers because they tend to be forgotten about and as a result obscure what is really going on. Perhaps I'm missing something, but inn this case, your indexed view would obscure what is really going on even more than a trigger. You might forger to look at the triggers, but eventually think of it, but who would think to check the views?
KM
KM,I agree that a unique index is more difficult to overlook
AlexKuznetsov
You don't need a view, indexed or otherwise, to implement the "calculated column + unique index" idea -- just plop it on the source table and you're done.
Philip Kelley