暂时开启级联删除

1 sql-server

以前可能有人问过这个问题,但我找不到答案。

我在 sql-server-20XX (更可能是旧的)中有一个复杂的数据模型,其中级联删除已被关闭(按设计)。

我们的数据库的很大一部分已经被出售(我可以在顶层进行区分),并且我们有理由想要删除这些数据。我想在一个脚本中执行此操作,其中我首先打开级联删除,然后删除所有数据,然后再次关闭级联删除,但我找不到正确的语法。

需要提及的相关事项:

  • 我想从总共 10-15 个表中删除大约 40% 的行,这些行都以某种方式 1:n-1:n...-耦合到组织表
  • 性能无关紧要,因为该应用程序不在工作时间之外使用

这些表必须保留,我需要删除组织表中的 10/33 行,并且需要删除与这些组织耦合的订单、用户...等中的所有行

AMt*_*two 7

SQL Server 不允许您ALTER CONSTRAINT更改现有约束以级联删除。然而,SQL Server 很乐意让您创建相同的外键约束。因此,您不必打乱现有的 FK 约束,只需创建一些新的约束,这将帮助您进行清理,然后在完成后可以删除这些特殊用途的约束。

示例数据

考虑这个非常简单的模式:

CREATE DATABASE [CascadeOnDelete];
GO

USE [CascadeOnDelete];

CREATE TABLE dbo.Customers (
    CustomerID int,
    CustomerName nvarchar(100),
    CONSTRAINT PK_Customers PRIMARY KEY CLUSTERED (CustomerID)
    );
INSERT dbo.Customers (CustomerID, CustomerName)
VALUES (1,'Grandpa Joe'),(2,'Grandma Josephine'),
        (3,'Grandpa George'),(4,'Grandma Gerorgina'),
        (5,'Charlie Bucket');

CREATE TABLE dbo.Orders(
    OrderID int,
    CustomerID int
        CONSTRAINT FK_Orders_CustomerID 
        REFERENCES Customers(CustomerID),
    OrderDetails nvarchar(100),
    CONSTRAINT PK_Parents PRIMARY KEY CLUSTERED (OrderID)
    );
INSERT dbo.Orders (OrderID, CustomerID, OrderDetails)
VALUES (1,1,'Wonka bar'),
       (2,5,'Wonka bar'),
       (3,5,'Wonka Scrumdiddlyumptious Bar');

SELECT * FROM Customers;
SELECT * FROM Orders;
Run Code Online (Sandbox Code Playgroud)

如果您尝试删除 Grampa Joe,您会收到错误消息,因为 Grampa Joe 有一个订单:

DELETE c
FROM dbo.Customers AS c
WHERE CustomerID = 1;

/*
Msg 547, Level 16, State 0, Line 33
The DELETE statement conflicted with the REFERENCE constraint "FK_Orders_CustomerID". The conflict occurred in database "CascadeOnDelete", table "dbo.Orders", column 'CustomerID'.
*/
Run Code Online (Sandbox Code Playgroud)

现在我们已经完成了设置,我们可以编写我们需要的其余内容的脚本:

解决方案

首先,我们可以使用一些动态 SQL 为数据库中的每个 FK 生成脚本。我正在附加名称以_CASCADEDELETES识别它们。我也在创建这些WITH NOCHECKON DELETE CASCADE无论原始的 FK 定义如何。您将获取此脚本的输出,检查它,然后运行它以复制数据库中的每个 FK,并添加以下内容:

--adapted from https://www.mssqltips.com/sqlservertip/3347/drop-and-recreate-all-foreign-key-constraints-in-sql-server/
SELECT N'ALTER TABLE ' 
       + QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name) + N' WITH NOCHECK'
       + N'    ADD CONSTRAINT ' + QUOTENAME(fk.name + N'_CASCADEDELETES') 
       + N'    FOREIGN KEY (' 
            + STUFF((SELECT ',' + QUOTENAME(c.name)
                    FROM sys.columns AS c 
                    JOIN sys.foreign_key_columns AS fkc 
                        ON fkc.parent_column_id = c.column_id
                        AND fkc.parent_object_id = c.object_id
                    WHERE fkc.constraint_object_id = fk.object_id
                    ORDER BY fkc.constraint_column_id 
                    FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'')
        + N') REFERENCES ' + QUOTENAME(rs.name) + '.' + QUOTENAME(rt.name)
        + N'(' 
            + STUFF((SELECT ',' + QUOTENAME(c.name)
                     FROM sys.columns AS c 
                     JOIN sys.foreign_key_columns AS fkc 
                        ON fkc.referenced_column_id = c.column_id
                        AND fkc.referenced_object_id = c.[object_id]
                    WHERE fkc.constraint_object_id = fk.[object_id]
                    ORDER BY fkc.constraint_column_id 
                    FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') 
        + N')'
        + N' ON DELETE CASCADE;'
FROM sys.foreign_keys AS fk
INNER JOIN sys.tables AS rt -- referenced table
  ON fk.referenced_object_id = rt.[object_id]
INNER JOIN sys.schemas AS rs 
  ON rt.[schema_id] = rs.[schema_id]
INNER JOIN sys.tables AS ct -- constraint table
  ON fk.parent_object_id = ct.[object_id]
INNER JOIN sys.schemas AS cs 
  ON ct.[schema_id] = cs.[schema_id]
WHERE rt.is_ms_shipped = 0 AND ct.is_ms_shipped = 0;
Run Code Online (Sandbox Code Playgroud)

在我们的示例数据库中,我们只有一个 FK,因此输出会为该一个 FK 生成一个新的 FK:

ALTER TABLE [dbo].[Orders] WITH NOCHECK
    ADD CONSTRAINT [FK_Orders_CustomerID_CASCADEDELETES] FOREIGN KEY ([CustomerID]) 
        REFERENCES [dbo].[Customers]([CustomerID]) 
        ON DELETE CASCADE;
Run Code Online (Sandbox Code Playgroud)

创建该 FK 后,我们可以简单地再次运行原始删除,从表中删除 Grampa Joe Customers,它也会级联并删除Orders

DELETE c
FROM dbo.Customers AS c
WHERE CustomerID = 1;

SELECT *
FROM dbo.Orders
Run Code Online (Sandbox Code Playgroud)

我们就完成了!最后一步是简单地删除我们创建的重复 FK。我们可以使用另一个脚本来生成DROP命令,就像我们在创建时所做的那样。我们可以简单地查看并运行此脚本的输出,以根据_CASCADE DELETES我们添加到名称的附加内容删除 FK:

--adapted from https://www.mssqltips.com/sqlservertip/3347/drop-and-recreate-all-foreign-key-constraints-in-sql-server/
SELECT N'
ALTER TABLE ' + QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name) 
    + ' DROP CONSTRAINT ' + QUOTENAME(fk.name) + ';'
FROM sys.foreign_keys AS fk
INNER JOIN sys.tables AS ct
  ON fk.parent_object_id = ct.[object_id]
INNER JOIN sys.schemas AS cs 
  ON ct.[schema_id] = cs.[schema_id]
WHERE fk.name LIKE '%CASCADEDELETES';
Run Code Online (Sandbox Code Playgroud)

这样做的好处是,您不必担心如何将原始 FK 恢复到开始时的状态,因为您永远不必碰它们。如果您的所有 FK 都完全受信任,并且您希望避免重新创建所有 FKWITH CHECK以确保它们最终受信任的潜在阻塞或运行时问题,这可能特别有用。