3

I'm creating a table that'll have a single bit not null column IsDefault. I need to write a constraint that'll make sure there'll be only one default value per UserId (field in the same table). I can't use unique constraint on this because it is possible to have many non-default values.

What is the best approach to do this using MS SQL Server 2008?

Thanks.

4

7 回答 7

3

我看到的最简单的方法是使用 UDF(用户定义函数)的检查约束。

例如,看这里。 http://sqljourney.wordpress.com/2010/06/25/check-constraint-with-user-defined-function-in-sql-server/

未经测试的例子

CREATE FUNCTION dbo.CheckDefaultUnicity(@UserId int)
RETURNS int
AS 
BEGIN
   DECLARE @retval int
   SELECT @retval = COUNT(*) FROM <your table> where UserId = @UserId and <columnwithDefault> = 1-- or whatever is your default value
   RETURN @retval 
END;
GO

改变你的桌子

ALTER TABLE <yourTable> 
ADD CONSTRAINT Ck_UniqueDefaultForUser 
CHECK (dbo.CheckDefaultUnicity(UserId) <2)
于 2012-06-11T11:20:23.707 回答
1

Check Constraint 肯定会起作用,但在我看来,这不是一个好的设计选择。原因是您的 UDF 约束将类似于

SELECT @Count = COUNT(UserId) 
FROM   User
WHERE  IsDefault = 1
GROUP BY UserId
HAVING COUNT(UserId) > 1

IF @Count > 0 
  ....'FAIL

由于这涉及 2 列,因此需要成为表级别约束,并且您拥有的记录越多,插入/更新/删除的速度就越慢。

更好的选择是只允许通过存储过程访问该表,因此在插入/更新之前,您可以非常快速地运行

IF EXISTS(SELECT UserId FROM User where UserId = @UserId and IsDefault = 1)

在您的插入/更新/删除之前

但是,我可以理解您可能正在使用 ORM,并且可能不希望在您的系统中使用存储过程,因此您可以将表的设计更改为下面。这假设

tblUser:UserId、FirstName、Suraname 等

tblUserDefault:UserId(唯一约束)

我不确定 IsDefault 在您的系统中代表什么,所以我在上面假设用户是默认的或不是的。任何人都可以将其用作参考。它允许您在不使用 USP 或可怕的全表检查约束(或触发器)的情况下强制执行约束,并且可以在任何体面的 ORM 中映射

于 2012-06-11T11:37:42.260 回答
1

另一个相对简单的选择是使用CLUSTERED INDEXED VIEW. 这样做的要点是

  • 从视图中UserID的表中选择所有的。IsDefault=1
  • 添加唯一索引UserID

聚集索引视图

CREATE VIEW dbo.VIEW_Users_IsDefault WITH SCHEMABINDING AS 
  SELECT  UserID, IsDefault
  FROM    dbo.Users
  WHERE   IsDefault = 1
GO  

CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_USERS_ISDEFAULT 
  ON dbo.VIEW_Users_IsDefault (UserID)
GO

测试脚本

BEGIN TRAN

CREATE TABLE dbo.Users (UserID INT, IsDefault BIT)
GO

CREATE VIEW dbo.VIEW_Users_IsDefault WITH SCHEMABINDING AS 
  SELECT  UserID, IsDefault
  FROM    dbo.Users
  WHERE   IsDefault = 1
GO  

CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_USERS_ISDEFAULT ON dbo.VIEW_Users_IsDefault (UserID)
GO

INSERT INTO dbo.Users VALUES (1, 0)
INSERT INTO dbo.Users VALUES (1, 1)
INSERT INTO dbo.Users VALUES (1, 1)  -- Fails because of clustered index

ROLLBACK TRAN
于 2012-06-11T11:36:10.253 回答
0

到目前为止,我认为任何答案都没有任何问题,CHECK CONSTRAINTS但是TRIGGERS,这对我来说似乎有点倒退。

我只能假设UserID您的表中是一个表的外键User,那么为什么不在您的用户表中添加一列来存储默认的 CardID,而不是将其标记为默认值呢?这使得用户不可能在没有代价高昂的触发器/约束的情况下拥有多个默认 CardID。如果您使该列不可为空,那么如果您愿意,用户也不可能没有默认 CardID。

于 2012-06-11T11:38:57.207 回答
0

怎么样CHECK Constraints。见这里: http: //msdn.microsoft.com/en-us/library/ms188258 (v=sql.105).aspx

ALTER TABLE yourtable
ADD CONSTRAINT IsDefaultChecked CHECK (IsDefault = T );
于 2012-06-11T11:20:03.723 回答
0

虽然我认为触发器和约束解决方案更好,但如果您通过存储过程控制插入/更新,一种更简单的方法是首先更新冲突的行(假设新的默认值总是获胜):

ALTER PROCEDURE dbo.UserWhateverTable_<action>
  @UserID INT,
  @CardID INT
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE dbo.UserWhatever 
      SET IsDefault = 0 
      WHERE UserID = @UserID
        AND CardID = @CardID
        AND IsDefault = 1;

    -- insert or update here
END
GO

事实上,这样做可能不是一个坏主意(因此业务逻辑在您的 DML 过程中是 100% 清晰的),除了使用触发器或约束来保护它(以捕获在您的过程之外进行更新的情况) .

于 2012-06-11T11:50:03.343 回答
0

在这种情况下,您可能需要使用触发器。当用户更改他们的默认值时,触发器可以自动将当前用户的当前默认值翻转为 false。

基本上,使用 AFTER insert/update 触发器将 IsDefault 列设置为 0,以便在 IsDefault 值设置为 1 的插入/更新中的任何用户。

CREATE TRIGGER dbo.tr_default
ON dbo.MyTable
AFTER INSERT, UPDATE 
AS

  if(exists(select * from inserted where IsDefault = 1)
  begin

      update dbo.MyTable
        set IsDefault = 0
      from inserted i
      join dbo.MyTable t on i.userid = t.userid
      where i.IsDefault = 1
        and i.TheValue != t.TheValue

  end
于 2012-06-11T11:31:40.910 回答