当前位置: 首页 > 面试题库 >

在SQL中级联菱形删除

陈浩
2023-03-14
问题内容

如果我的数据库中有一个简单的User表,而有一个以User.id作为外键的简单Item表,则:

(id UNIQUEIDENTIFIER DEFAULT (NEWID()) NOT NULL,
name NVARCHAR (MAX) NULL,
email NVARCHAR (128) NULL,
authenticationId NVARCHAR (128) NULL,
createdAt DATETIME DEFAULT GETDATE() NOT NULL,
PRIMARY KEY (id))

CREATE TABLE Items
(id UNIQUEIDENTIFIER DEFAULT (NEWID()) NOT NULL,
userId UNIQUEIDENTIFIER NOT NULL,
name NVARCHAR (MAX) NULL,
description NVARCHAR (MAX) NULL,
isPublic BIT DEFAULT 0 NOT NULL,
createdAt DATETIME DEFAULT GETDATE() NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (userId) REFERENCES Users (id))

如果将用户从表中删除,我需要先删除所有相关项,以免破坏参照完整性约束。这很容易做到CASCADE DELETE

CREATE TABLE Items
(id UNIQUEIDENTIFIER DEFAULT (NEWID()) NOT NULL,
userId UNIQUEIDENTIFIER NOT NULL,
name NVARCHAR (MAX) NULL,
description NVARCHAR (MAX) NULL,
isPublic BIT DEFAULT 0 NOT NULL,
createdAt DATETIME DEFAULT GETDATE() NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (userId) REFERENCES Users (id) ON DELETE CASCADE)

但是,如果我也有引用用户的集合,还有一个将项目收集到集合中的表,那么我很麻烦,即以下附加代码不起作用。

CREATE TABLE Collections
(id UNIQUEIDENTIFIER DEFAULT (NEWID()) NOT NULL,
userId UNIQUEIDENTIFIER NOT NULL,
name NVARCHAR (MAX) NULL,
description NVARCHAR (MAX) NULL,
isPublic BIT DEFAULT 0 NOT NULL,
layoutSettings NVARCHAR (MAX) NULL,
createdAt DATETIME DEFAULT GETDATE() NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (userId) REFERENCES Users (id) ON DELETE CASCADE)

CREATE TABLE CollectedItems
(itemId UNIQUEIDENTIFIER NOT NULL,
collectionId  UNIQUEIDENTIFIER NOT NULL,
createdAt DATETIME DEFAULT GETDATE() NOT NULL,
PRIMARY KEY CLUSTERED (itemId, collectionId),
FOREIGN KEY (itemId) REFERENCES Items (id) ON DELETE CASCADE,
FOREIGN KEY (collectionId) REFERENCES Collections (id) ON DELETE CASCADE)

该错误表明“可能导致循环或多个级联路径”。我认为推荐的解决方法是

  1. 重新设计表格,但是我看不到如何做。或者,通常被称为“不得已而为之”
  2. 使用触发器。

因此,我删除了ON DELETECASCADE并改为使用触发器(文档),如下所示:

CREATE TRIGGER DELETE_User
   ON Users
   INSTEAD OF DELETE
AS 
BEGIN
 SET NOCOUNT ON
 DELETE FROM Items WHERE userId IN (SELECT id FROM DELETED)
 DELETE FROM Collections WHERE userId IN (SELECT id FROM DELETED)
 DELETE FROM Users WHERE id IN (SELECT id FROM DELETED)
END

CREATE TRIGGER DELETE_Item
   ON Items
   INSTEAD OF DELETE
AS 
BEGIN
 SET NOCOUNT ON
 DELETE FROM CollectedItems WHERE itemId IN (SELECT id FROM DELETED)
 DELETE FROM Items WHERE id IN (SELECT id FROM DELETED)
END

CREATE TRIGGER DELETE_Collection
   ON Collections
   INSTEAD OF DELETE
AS 
BEGIN
 SET NOCOUNT ON
 DELETE FROM CollectedItems WHERE collectionId IN (SELECT id FROM DELETED)
 DELETE FROM Collections WHERE id IN (SELECT id FROM DELETED)
END

但是,尽管失败了,但还是失败了。我有一堆单元测试(用xUnit编写)。个别情况下,测试始终会通过。但是,由于SQL死锁,大量运行会随机失败。


问题答案:

我更喜欢 没有
自动级联操作,无论是DELETE还是UPDATE。只是为了省心。想象一下,您已经配置了级联删除,然后由于某些错误而试图删除错误的用户,即使数据库中有与之相关的数据,您的程序也会被删除。相关表中的所有相关数据都将消失,而不会发出任何警告。

通常,我确保首先使用明确的单独过程删除所有相关数据,每个相关表一个,然后再删除主表中的行。删除将成功,因为在引用表中没有子行。

对于您的示例,我将有一个DeleteUser带有一个参数的专用存储过程UserID,该存储过程知道哪些表与用户相关,并且应按什么顺序删除详细信息。此过程经过测试,是删除用户的唯一方法。如果程序的其余部分错误地尝试直接从Users表中删除行,那么如果相关表中有一些数据,则此尝试将失败。如果错误删除的用户没有任何详细信息,则尝试会进行,但是至少您不会丢失很多数据。

对于您的架构,过程可能如下所示:

CREATE PROCEDURE dbo.DeleteUser
    @ParamUserID int
AS
BEGIN
    SET NOCOUNT ON; SET XACT_ABORT ON;

    BEGIN TRANSACTION;
    BEGIN TRY
        -- Delete from CollectedItems going through Items
        DELETE FROM CollectedItems
        WHERE CollectedItems.itemId IN
        (
            SELECT Items.id
            FROM Items
            WHERE Items.userId = @ParamUserID
        );

        -- Delete from CollectedItems going through Collections
        DELETE FROM CollectedItems
        WHERE CollectedItems.collectionId IN
        (
            SELECT Collections.id
            FROM Collections
            WHERE Collections.userId = @ParamUserID
        );

        -- Delete Items
        DELETE FROM Items WHERE Items.userId = @ParamUserID;

        -- Delete Collections
        DELETE FROM Collections WHERE Collections.userId = @ParamUserID;

        -- Finally delete the main user
        DELETE FROM Users WHERE ID = @ParamUserID;

        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
        ...
        -- process the error
    END CATCH;
END

如果您真的想设置级联删除,那么我将只为表定义 一个触发器Users。同样,将没有外键具有级联删除功能,但是Users表上的触发器将具有与上述过程非常相似的逻辑。



 类似资料:
  • 问题内容: 我是SQL新手,遇到了一个奇怪的问题。所以我有两个表OFFERS和SUPPLIER。这是供应商表。 这是OFFERS表。 所以现在,当我尝试像这样向offers表添加外键约束时 我收到一条错误消息:“ ORA-01735:无效的ALTER TABLE选项”。如果我删除最后一行,即“ ON UPDATE CASCADE”,则可以正常工作。那么,我在做什么错呢?我在互联网上看到了很多类似的

  • 问题内容: 我正在开发使用sqlalchemy 0.6的现有应用程序的扩展。 该应用程序具有以非声明方式创建的sqlalchemy表。我正在尝试在扩展程序中创建一个新表,该表的外键列指向应用程序数据库中主表的主键,并且以声明方式创建它。 这一切都很好,加载扩展程序后就创建了表,一点也没有抱怨。我的表将打印出来,并演示已经添加了新行。我想要并认为可能的(但不知道,因为我从未使用过sql或任何其他数据

  • 问题内容: 我必须缺少SQLAlchemy的层叠选项的琐碎内容,因为我无法获得简单的层叠删除来正确操作-如果删除了父元素,则子对象将使用外键保留。 我在这里放了一个简洁的测试用例: 输出: 父母与子女之间存在简单的一对多关系。该脚本创建一个父级,添加3个子级,然后提交。接下来,它删除父级,但子级仍然存在。为什么?如何使孩子级联删除? 问题答案: 问题是sqlalchemy认为是父级的,因为这是您定

  • 主要内容:JPA级联删除示例,输出结果级联移除用于指定如果父实体被移除,则其所有相关实体也将被移除。 以下语法用于执行级联删除操作 - JPA级联删除示例 在这个例子中,我们将创建两个相互关联的实体类,但要建立它们之间的依赖关系,我们将执行级联操作。 这个例子包含以下步骤 - 第1步: 在包下创建一个名为的实体类,其中包含属性:,,以及标记为级联规范的类型的对象。 文件: StudentEntity.java - 第2步: 在包下创建

  • 级联删除通常被用作数据库术语,用来描述删除一个数据行时自动删除关联的数据行的特征。EF Core 实现了一些不同的删除行为,并且允许对关联关系的删除行为进行配置。EF Core 还实现了相关的惯例,它会基于关系的必要性为每个关系配置有用的默认删除行为。 删除行为 删除行为是在 DeleteBehavior 枚举中定义的,可以将它传递给 OnDelete 流式 API 来控制主实体/父实体的删除是否

  • 在数据库中,将有一个列,并对的列具有外键约束。 对于在删除时级联的外键约束,可能执行的一些操作包括、和。这给出了以下方案组合。 a:在JPA中对父级进行级联删除调用,在删除父行时删除数据库中的子级。 b:在JPA中级联删除,在父删除时将子表中的外键列设置为null。在这种情况下,@joincolumn中的和中的可能需要为true/false。 C:在JPA中进行级联删除,在数据库中对外键删除不做任