当我想使用数据库约束但只标记为已删除而不是删除时该怎么办?

时间:2009-04-21 05:55:49

标签: sql sql-server-2005 tsql constraints

我正在一个不删除数据库项目但只标记为已删除的项目中。像这样:

id   name     deleted
---  -------  --------
1    Thingy1  0
2    Thingy2  0
3    Thingy3  0

我希望能够在name列上定义类似UNIQUE约束的内容。看起来很简单吧?

让我们假设一个场景,其中“Thingy3”被删除,并且创建了一个新的(可能在几年之后)。我们得到:

id   name     deleted
---  -------  --------
1    Thingy1  0
2    Thingy2  0
3    Thingy3  1
...
100  Thingy3  0

从用户的角度来看,他删除了一个项目并创建了一个新项目。就像删除文件和创建新文件一样。因此,对他来说很明显,新项目与连接到旧项目的任何数据无关,也不相关。

已经处理完了,因为数据库只关心id,并且因为新项目的id为100而不是3,所以它们完全不同。

当我想阻止用户创建另一个“Thingy3”项目时,我遇到了困难。如果我有一个UNIQUE约束只查看未标记为deleted的项目,那么我就解决了一个问题。

(当然,那时我必须处理当有人撤消删除时会发生什么......)

那么,我该如何定义那种约束呢?

10 个答案:

答案 0 :(得分:12)

您可以在删除记录时将id值添加到名称的末尾,因此当有人删除id 3时,名称将变为Thingy3_3,然后当他们删除id 100时,名称将变为Thingy3_100。这将允许您在名称和已删除字段上创建唯一的复合索引,但是只要您显示名称列,就必须过滤名称列,并从名称末尾删除ID。

或许更好的解决方案是使用DATETIME类型的deleted_at列替换已删除的列。然后,您可以在名称上保留唯一索引并删除,其中未删除的记录在deleted_at字段中具有空值。这样可以防止在活动状态下创建多个名称,但允许您多次删除相同的名称。

显然需要在取消删除记录时进行测试,以确保在允许取消删除之前没有具有相同名称的行和null deleted_at字段。

通过使用INSTEAD-OF触发器进行删除,您实际上可以在数据库中实现所有这些逻辑。此触发器不会删除记录,而是在删除记录时更新deleted_at列。

以下示例代码演示了此

CREATE TABLE swtest (  
    id          INT IDENTITY,  
    name        NVARCHAR(20),  
    deleted_at  DATETIME  
)  
GO  
CREATE TRIGGER tr_swtest_delete ON swtest  
INSTEAD OF DELETE  
AS  
BEGIN  
    UPDATE swtest SET deleted_at = getDate()  
    WHERE id IN (SELECT deleted.id FROM deleted)
    AND deleted_at IS NULL      -- Required to prevent duplicates when deleting already deleted records  
END  
GO  

CREATE UNIQUE INDEX ix_swtest1 ON swtest(name, deleted_at)  

INSERT INTO swtest (name) VALUES ('Thingy1')  
INSERT INTO swtest (name) VALUES ('Thingy2')  
DELETE FROM swtest WHERE id = SCOPE_IDENTITY()  
INSERT INTO swtest (name) VALUES ('Thingy2')  
DELETE FROM swtest WHERE id = SCOPE_IDENTITY()  
INSERT INTO swtest (name) VALUES ('Thingy2')  

SELECT * FROM swtest  
DROP TABLE swtest  

从此查询中选择将返回以下内容

id      name       deleted_at
1       Thingy1    NULL
2       Thingy2    2009-04-21 08:55:38.180
3       Thingy2    2009-04-21 08:55:38.307
4       Thingy2    NULL

因此,在您的代码中,您可以使用普通删除删除记录,并让触发器处理细节。唯一可能的问题(我可以看到)是删除已删除的记录可能会导致重复行,因此触发条件不会更新已删除行上的deleted_at字段。

答案 1 :(得分:3)

使用“回收站”表可能值得考虑。不是将旧记录与标志保持在同一个表中,而是使用自己的约束将它们移动到自己的表中。例如,在活动表中,您对名称有一个UNIQUE约束,但在回收站表中则没有。

答案 2 :(得分:2)

复合唯一约束的问题在于,不可能有多个具有相同名称的记录被删除。这意味着一旦删除第三条记录,系统就会中断。我不建议在名称中附加内容,因为从理论上讲,可能会出现重复的情况。此外,通过这样做,您基本上破坏了数据库中的数据,并为数据本身添加了神秘的业务逻辑。

数据库范围内唯一可能的解决方案是添加一个触发器,用于检查插入/更新的数据是否有效。也可以将数据库之外的检查放入代码中。

答案 3 :(得分:1)

例如,您可以向已删除的名称添加非法字符(*)。但是你仍然有问题取消删除已删除的项目。因此,更好的想法是禁止双重名称,即使它们被删除。

您可以在一段时间后清理已删除的记录(或将它们移动到单独的表格中)。

答案 4 :(得分:1)

对于名为[Test]的简单表,其中包含列ID(int),Filter(nvarchar),Deleted(bit)

ALTER TABLE [dbo].[Test] ADD 
    CONSTRAINT [DF_Test_Deleted] DEFAULT (0) FOR [Deleted],
    CONSTRAINT [IX_Test] UNIQUE  NONCLUSTERED 
    (
        [filter],
        [Deleted]
    )  ON [PRIMARY] 

答案 5 :(得分:1)

在唯一名称后添加随机哈希。一些容易逆转的东西。可能与下划线或其他角色分开。

评论后编辑:您只需添加下划线和当前时间戳。

答案 6 :(得分:1)

而不是删除列使用end_date列。当用户删除记录时,在end_date列中添加当前日期。 end_date列为NULL的任何记录都是您当前的记录。在两列name和end_date上定义唯一约束。由于此约束,您永远不会有重复有效记录名称的方案。每当用户想要取消删除记录时,您需要将end_date列设置为null,如果这违反了唯一约束,那么您向用户显示一条消息,该用户已经存在相同的名称。

答案 7 :(得分:1)

您需要的约束类型是表级CHECK约束,即CHECK约束由子查询组成,该子查询为表测试NOT EXISTS(或等效),例如

CREATE TABLE Test 
(
   ID INTEGER NOT NULL UNIQUE, 
   name VARCHAR(30) NOT NULL, 
   deleted INTEGER NOT NULL, 
   CHECK (deleted IN (0, 1))
);

ALTER TABLE Test ADD
   CONSTRAINT test1__unique_non_deleted
      CHECK 
      (
         NOT EXISTS 
         (
            SELECT T1.name
              FROM Test AS T1
             WHERE T1.deleted = 0
             GROUP
                BY T1.Name
            HAVING COUNT(*) > 1
         )
      );

INSERT INTO Test (ID, name, deleted) VALUES (1, 'Thingy1', 0)
;
INSERT INTO Test (ID, name, deleted) VALUES (2, 'Thingy2', 0)
;
INSERT INTO Test (ID, name, deleted) VALUES (3, 'Thingy3', 1)
;
INSERT INTO Test (ID, name, deleted) VALUES (4, 'Thingy3', 1)
;
INSERT INTO Test (ID, name, deleted) VALUES (5, 'Thingy3', 0)
;
INSERT INTO Test (ID, name, deleted) VALUES (6, 'Thingy3', 0)
;

最后INSERT(ID = 6)会导致约束咬合而INSERT将失败。 Q.E.D。

...哦,差点忘了提一下:SQL Server还不支持包含子查询的CHECK约束(我在ACE / JET上测试了上面的内容,a.k.a。)。虽然可以使用FUNCTION我已经读过,但这是不安全的,因为SQL Server测试约束是逐行的(参见David Portas' Blog)。在SQL Server支持完整的SQL-92功能之前,我首选的解决方法是在触发器中使用相同的逻辑。

答案 8 :(得分:0)

不确定SQL Server 2005,但您可以定义复合约束/索引

CREATE UNIQUE INDEX [MyINDEX] ON [TABLENAME] ([NAME] , [DELETED])

正如Steve tWeet所指出的,这只会让你删除/创建两次。

答案 9 :(得分:0)

在(名称,已删除)上创建唯一约束。这意味着每个名字只能删除一个。

的明显解决方法在ANSI-92下运行但在MS SQLServer上运行:https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=299229