我正在创建一个只有一位非空列IsDefault
的表。我需要编写一个约束,确保每个UserId
(同一个表中的字段)只有一个默认值。
我不能对此使用唯一约束,因为它可能有许多非默认值。
使用MS SQL Server 2008执行此操作的最佳方法是什么?
感谢。
答案 0 :(得分:3)
我看到的最简单方法是使用UDF(用户定义函数)检查约束。
未经测试的例子
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)
答案 1 :(得分: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
答案 2 :(得分:1)
检查约束肯定会起作用,但在我看来,这不是一个好的设计选择。原因是您的约束的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并且可能不希望在系统中安装Stored Procs,因此您可以将表的设计更改为以下内容。这假定
tblUser:UserId,FirstName,Suraname等
tblUserDefault: UserId (唯一约束)
我不确定IsDefault在你的系统中代表什么,所以我在上面假设用户是默认的或不是默认的。任何人都可以使用它作为参考。它允许您在不使用USP或可怕的表范围检查约束(或触发器)的情况下强制执行约束,并且可以在任何正常的ORM中映射
答案 3 :(得分:0)
CHECK Constraints
怎么样?见这里:http://msdn.microsoft.com/en-us/library/ms188258(v=sql.105).aspx
ALTER TABLE yourtable
ADD CONSTRAINT IsDefaultChecked CHECK (IsDefault = T );
答案 4 :(得分:0)
在这种情况下,您可能希望使用触发器。当用户更改其默认值时,触发器可以自动将当前用户的当前默认值翻转为false。
基本上,对于插入/更新中IsDefault值设置为1的任何用户,使用AFTER插入/更新触发器将IsDefault列设置为0。
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
答案 5 :(得分:0)
到目前为止,我认为CHECK CONSTRAINTS
和TRIGGERS
的任何答案都没有任何问题,但对我来说这似乎是一个倒退的解决方案。
我只能假设您的表中的UserID
是User
表的外键,那么为什么不在User表中添加一列来存储默认的CardID,而不是将其标记为默认值?这使得用户无法在没有昂贵的触发器/约束的情况下拥有多个默认CardID。如果你使列不可为空,那么如果你愿意的话,用户也不可能没有默认的CardID。
答案 6 :(得分: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%清晰)可能并不是一个坏主意(以捕获更新在外部进行更新的情况)你的程序)。