SQL Server表上的唯一分组约束

时间:2014-09-09 08:50:05

标签: sql sql-server tsql sql-server-2008-r2

在T-SQL中,我遇到一个约束,它将阻止为该标志插入重复记录。

对于每个[FK_Something],标志[MyFlag]只能设置为值1,但可以多次设置为0.

这是否可能,如果不使用触发器就是如此?

我使用的是SQL Server 2008 R2。

set nocount on

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MyTable]') AND type in (N'U'))
    DROP TABLE [dbo].[MyTable]
GO

CREATE TABLE [dbo].[MyTable]
(
    [id]            [int] IDENTITY(1,1) NOT NULL,
    [FK_Something]  [int] NOT NULL,
    [MyFlag]        [bit] NOT NULL
) ON [PRIMARY]
GO

--  Valid inserts   --
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (1,0);
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (1,1);
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (1,0);
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (2,0);
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (2,0);
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (2,0);
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (2,1);

---------------------------------------
-- Inserts Should Fail  
-- As [MyFlag] set for [FK_Something]
---------------------------------------
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (1,1)   
GO

INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (2,1)
GO

-- Constraint should apply this logic --
Declare @FK_Something   INT
Declare @MyFlag         INT
set @FK_Something = 2
Set @MyFlag       = 0

if( @myflag = 0 OR
    not exists(Select 1 from [MyTable] where FK_Something = @FK_Something and MyFlag = @MyFlag))
BEGIN
    INSERT INTO [MyTable]   ([FK_Something],[MyFlag])  VALUES  (@FK_Something,@MyFlag);
    print 'Inserted ';
END
GO

select * from [MyTable]

3 个答案:

答案 0 :(得分:2)

指数。

  

对于每个[FK_Something],标志[MyFlag]只能设置一次   值1但可以多次设置为0。

这是1个simle索引:

  • FK_Something和MyFlag 1 - 唯一

这里的技巧是不对所有行应用唯一索引,这就是添加过滤器的原因。

http://msdn.microsoft.com/en-us/library/cc280372.aspx

解释了如下语法:

CREATE UNIQUE NONCLUSTERED INDEX IX_Whatever
    ON MyTable (FK_Something, myFlag)
    WHERE MyFlag = 1 

比约束更简单并保证可以正常工作。

答案 1 :(得分:0)

您可以尝试在检查约束中创建用户定义的函数:

CREATE FUNCTION ChkFn(
    @FK_Something INT
)
RETURNS INT
AS 
BEGIN
    DECLARE @MyFlag         INT
    SET @MyFlag       = 0

    IF( @myflag = 0 OR
        NOT EXISTS( SELECT 1 
                    FROM [MyTable]
                    WHERE FK_Something = @FK_Something 
                    AND MyFlag = @MyFlag))
    BEGIN
        SET @retval=1;
    END

    RETURN @retval
END;
GO

ALTER TABLE myTable
ADD CONSTRAINT chkFkPk CHECK (dbo.ChekFn(FK_Something) = 1 );
GO

尝试一下,让我知道它是否有效。 我没有测试过这个。

请注意,对于多行更新或快照隔离可能会失败。

答案 2 :(得分:0)

感谢Mack建议使用功能。我不得不更改函数中的函数逻辑,因为它不允许插入第一个记录。我在下面发布了完整的整洁解决方案。 TomTom的索引​​解决方案不涉及功能,因此许多公司会更多地接受它。两者都在下面的代码中,有一个游戏,并根据您的需要选择合适的解决方案。

set nocount on
ALTER TABLE myTable drop CONSTRAINT chkFkPk 
go
drop  FUNCTION dbo.CheckMyTableFlag;
go
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MyTable]') AND type in (N'U'))
    DROP TABLE [dbo].[MyTable]
GO

CREATE TABLE [dbo].[MyTable]
(
    [id]            [int] IDENTITY(1,1) NOT NULL,
    [FK_Something]  [int] NOT NULL,
    [MyFlag]        [bit] NOT NULL
) ON [PRIMARY]
GO

/*
CREATE FUNCTION dbo.CheckMyTableFlag(@FK_Something INT)
    RETURNS INT
AS 
BEGIN

    Declare @retVal     INT = 0
    Select @retVal = sum(cast([myflag] as int)) from MyTable WHERE FK_Something = @FK_Something group by FK_Something

    RETURN @retVal
END;
GO


ALTER TABLE myTable ADD CONSTRAINT chkFkPk CHECK (dbo.CheckMyTableFlag(FK_Something) <2 );
*/
go
CREATE UNIQUE NONCLUSTERED INDEX IX_Whatever
    ON MyTable (FK_Something, myFlag)
    WHERE MyFlag = 1

go
----  Valid inserts   --
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (1,0); 
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (1,0);  
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (1,1);
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (1,0);
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (2,0);
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (2,0);
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (2,0);
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (2,1);

-----------------------------------------
---- Inserts Should Fail  
---- As [MyFlag] set for [FK_Something]
-----------------------------------------
INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (1,1)   
GO

INSERT INTO [MyTable]   ([FK_Something],[MyFlag])    VALUES  (2,1)
GO

select [id],[FK_Something],[MyFlag]  from mytable
go