Sql server 是否可以在SQL Server 2012中添加约束,以防止在软删除旧数据之前输入新数据?

Sql server 是否可以在SQL Server 2012中添加约束,以防止在软删除旧数据之前输入新数据?,sql-server,tsql,Sql Server,Tsql,我有下表: CREATE TABLE [Test] ( [Id] BIGINT IDENTITY(1, 1) NOT NULL, [Name] CHARACTER VARYING(255) NOT NULL, [DeletedOn] DATETIMEOFFSET NULL, UNIQUE([Name], [DeletedOn]), PRIMARY KEY([Id]) ); GO 我插入一个新记录,如下所示: INSERT INTO [Test] (

我有下表:

CREATE TABLE [Test]
(
    [Id] BIGINT IDENTITY(1, 1) NOT NULL,
    [Name] CHARACTER VARYING(255) NOT NULL,
    [DeletedOn] DATETIMEOFFSET NULL,

    UNIQUE([Name], [DeletedOn]),

    PRIMARY KEY([Id])
);

GO
我插入一个新记录,如下所示:

INSERT INTO [Test] (Name, DeletedOn) VALUES ('A record', SYSDATETIMEOFFSET())
使用此命令的后续插入将按预期完成。但是,我想要求在使用
DeletedOn
值比具有相同
Name
的现有记录更新的数据之前,只要表中存在具有相同
名称
的记录且该记录具有
NULL
DeletedOn
值,则拒绝该数据

另一种解释这种行为的方法是让您想象一个用户密码历史记录。用户输入密码,我的软件将其散列并存储在数据库中。用户密码过期且
DeletedOn
设置为当前日期和时间。我不希望用户再次输入相同的密码,所以这就是保存历史记录的目的。为了保持数据一致性,我想防止在已存在一个活动密码且该密码在
DeletedOn
列中没有值时添加密码。因此,如果我的软件出现错误行为,并试图将随机密码添加到用户的密码历史记录中,它应该会失败,因为它会违反某些约束,阻止删除不是单个活动密码的密码


我最初设想,如果尝试这种行为,我会将此逻辑封装在存储过程中并抛出一个错误,但我很好奇这是否可以用另一种方式完成。

据我所知,在软删除旧数据之前,没有现成的功能来阻止输入新数据?我们需要增加一些限制来防止这种情况。我们可以使用check约束来验证Name和Deleted On的组合是否存在

试试这个

CREATE FUNCTION [dbo].[chk_RecordExists](@Name Varchar(255))
RETURNS INT
AS
BEGIN
DECLARE @Result int
SET @Result = 0

DECLARE @id AS INT
SET @id=0
SELECT @id=MAX(ID) FROM [Test] WHERE Name = @Name

IF (@id=0)
    BEGIN
        SET @Result = 1 -- Allow to insert as its New record
    END
ELSE
BEGIN
-- Check the latest record for name if Deletedon is not null then its soft deleted can allow to insert new 
IF EXISTS (SELECT ID FROM [Test] WHERE ID=@id AND Name = @Name AND DeletedOn IS NOT NULL)
    BEGIN
        SET @Result = 1  -- Allow to insert as its old data is soft deleted
    END 
END

RETURN @Result
END
GO

ALTER TABLE Test WITH NOCHECK ADD CONSTRAINT [chk_Constraint]     CHECK(dbo.chk_RecordExists](Name)=0)

GO

我从来没有使用过用户
而不是
关键字,但我已经读到它可以用来做你想做的事情。下面是来自MSDN的示例代码,以防止在有旧记录时插入新记录。在
而不是

将上面的触发器更改为仅插入唯一行

CREATE TRIGGER IO_Trig_INS_TEST ON MyTestTable
    INSTEAD OF INSERT
AS
    BEGIN
        SET NOCOUNT ON            

        IF (
             NOT EXISTS ( SELECT P.SSN
                            FROM MyTestTable P
                               ,inserted I
                            WHERE P.SSN = I.SSN ) )
            INSERT INTO MyTestTable
                    SELECT FirstName
                           ,LastName
                           ,SSN
                        FROM ( SELECT FirstName
                                   ,LastName
                                   ,SSN
                                   ,ROW_NUMBER() OVER ( PARTITION BY SSN ORDER BY LastName, Firstname ) AS ROWNUM
                                FROM INSERTED ) A
                        WHERE A.ROWNUM = 1
        ELSE
            RAISERROR(N'You are trying to insert duplicate records',16,1)
    END

真遗憾,扳机坏了。它避免了常见的prat Fall(即许多人编写的中断触发器假定插入的
只包含一行),但随后由于不检查插入的
本身是否,包含重复的值。@Damien_不信者我已修改了答案,使其包含只插入唯一值的触发器。这非常简单,可将数据完整性检查保持在尽可能低的级别。我从来不知道你能在函数中定义复杂的检查。@MitchWheat,这是我在你的问题中看到的第二条评论,几乎没有解释。你能详细说明一下这是怎么打破的吗?我承认,我重写了这个实现来为我工作,并且从未测试过原始版本。然而,这个概念似乎相当可靠。你对此有何看法?评论是针对一个答案的。这是对你的一个问题的回答,这是巧合。在选择最大值并在其可能发生更改时使用它时,没有任何事务处理。这可能不是一个公认的答案,因为它可能会让人们误入歧途。@MitchWheat约束检查不是在
SERIALIZABLE
事务级别进行的,以强制执行ACID原则吗?一个
INSERT
被认为是一个语句,所有语句都是事务,所有事务都是原子操作。这不是我所指的。从具有活动插入的表中选择“最大值”。稍后从表中选择最大值,其值是否相同?
CREATE TRIGGER IO_Trig_INS_TEST ON MyTestTable
    INSTEAD OF INSERT
AS
    BEGIN
        SET NOCOUNT ON            

        IF (
             NOT EXISTS ( SELECT P.SSN
                            FROM MyTestTable P
                               ,inserted I
                            WHERE P.SSN = I.SSN ) )
            INSERT INTO MyTestTable
                    SELECT FirstName
                           ,LastName
                           ,SSN
                        FROM ( SELECT FirstName
                                   ,LastName
                                   ,SSN
                                   ,ROW_NUMBER() OVER ( PARTITION BY SSN ORDER BY LastName, Firstname ) AS ROWNUM
                                FROM INSERTED ) A
                        WHERE A.ROWNUM = 1
        ELSE
            RAISERROR(N'You are trying to insert duplicate records',16,1)
    END