如何动态创建触发器而不是删除,其中多个外键指向 SQL Server 中的同一个表?

问题描述 投票:0回答:1

我有以下表格

parent
child1
.

ParentID1

表中的
ParentID2
child1
指向
ParentID
中的
parent

架构:

-- Table creation logic
--parent table

CREATE TABLE [dbo].[Parent]
(
   [ParentID] [bigint] NOT NULL,
   [Data] [varchar](10) NOT NULL,

   CONSTRAINT [PK_Parent] 
       PRIMARY KEY CLUSTERED ([ParentID] ASC)
)
GO

-- Child table

CREATE TABLE [dbo].[Child1]
(
    [Child1ID] [bigint] NOT NULL,
    [ParentID1] [bigint] NULL,
    [ParentID2] [bigint] NULL,
    [Data] [varchar](10) NULL,

    CONSTRAINT [PK_Child1] 
        PRIMARY KEY CLUSTERED ([Child1ID] ASC)
)
GO

-- foreign key constraints
ALTER TABLE [dbo].[Child1] WITH CHECK 
    ADD CONSTRAINT [FK_Child1_ParentID1] 
        FOREIGN KEY ([ParentID1]) REFERENCES [dbo].[Parent] ([ParentID])
GO

ALTER TABLE [dbo].[child1] WITH CHECK 
    ADD CONSTRAINT [FK_Child1_ParentID2] 
        FOREIGN KEY ([ParentID2]) REFERENCES [dbo].[Parent] ([ParentID])
GO

-- inserting data into the tables
INSERT INTO Parent VALUES (1, 'test')
INSERT INTO Parent VALUES (2, 'test')
INSERT INTO Parent VALUES (3, 'test')
INSERT INTO Parent VALUES (4, 'test')
INSERT INTO Parent VALUES (5, 'test')
INSERT INTO Parent VALUES (6, 'test')
INSERT INTO Parent VALUES (7, 'test')
INSERT INTO Parent VALUES (8, 'test')
    
INSERT INTO Child1 VALUES (1, 1, 2, 'test')
INSERT INTO Child1 VALUES (2, 2, 3, 'test')
INSERT INTO Child1 VALUES (3, 3, 4, 'test')
INSERT INTO Child1 VALUES (4, 4, 5, 'test')
INSERT INTO Child1 VALUES (5, 5, 6, 'test')
INSERT INTO Child1 VALUES (6, 6, 7, 'test')
GO

每当我从父母那里删除一条记录时,我也想从孩子那里删除相应的记录。我尝试了“删除级联”但出现错误 - “可能导致循环或多个级联路径”。

所以,我开始创建触发器来处理这个问题。由于我有一个大数据库,我想在表上动态创建触发器。 因此,我循环访问以下元数据表 (LF_DB_Relations),其中包含 parenttable、childtable、primarykeycolumn 和 foreignkeycolumn 以动态创建触发器:

由于同一个父项和子项有多个父子条目,当它试图在同一个表上创建第二个“INSTEAD OF DELETE”触发器时创建触发器失败。

这是我用来动态创建触发器的查询:

DECLARE @childtable varchar(50);
DECLARE @fkcolumn varchar(50);
DECLARE @parenttable varchar(50);
DECLARE @pkcolumn varchar(50);

DECLARE @RowCnt BIGINT = 0;
DECLARE @count INT;
SET @count = 1;

-- get a count of total rows to process 
SELECT @RowCnt = COUNT(0) FROM dbo.LF_DB_Relations;

WHILE @count<= @RowCnt
BEGIN
    select
    @childtable = childtable,
    @fkcolumn = fkcolumn,
    @parenttable = parenttable,
    @pkcolumn = pkcolumn
    FROM dbo.LF_DB_Relations
    WHERE ID = @count

    EXEC('CREATE TRIGGER '+ 'Delete_' + @parenttable + '_Purging_' +@count+
       ' ON ' + @parenttable +
       ' INSTEAD OF DELETE ' +
        'AS ' +
        'BEGIN ' +
         'SET NOCOUNT ON; '+
         'DELETE FROM ' + @childtable +' WHERE ' + @fkcolumn +' IN (SELECT ' +  @pkcolumn + ' FROM DELETED) ' +
        ' DELETE FROM ' + @parenttable + ' WHERE ' + @pkcolumn + ' IN (SELECT ' +  @pkcolumn +' FROM DELETED) ' +
        'END');     

    PRINT @childtable;
   SET @count = @count + 1;
END;

我的动态触发查询有什么问题?我如何在每个父表上创建单个触发器,并删除所有子表的所有 FK 的查询?

sql sql-server triggers cascade
1个回答
0
投票

您多次引用

parent
表,因为在
child
中您有多个外键。因此,您的代码正在创建(或至少试图创建)多个
INSTEAD OF DELETE
触发器,这是不允许的。

假设数据看起来像:

并且您只需要为

Categories
表创建一个触发器。

所以你需要像这样对数据进行分组:

DROP TABLE IF EXISTS #LF_DB_Relations;

CREATE TABLE #LF_DB_Relations (
    childtable NVARCHAR(255),
    fkcolumn NVARCHAR(255),
    parenttable NVARCHAR(255),
    pkcolumn NVARCHAR(255)
);

INSERT INTO #LF_DB_Relations (childtable, fkcolumn, parenttable, pkcolumn)
VALUES ('OrderDetails', 'OrderID', 'Orders', 'OrderID'),
       ('OrderDetails', 'ProductID', 'Products', 'ProductID'),
       ('Products', 'CategoryID', 'Categories', 'CategoryID'),
       ('Products', 'SupplierID', 'Categories', 'CategoryID');

SELECT * FROM #LF_DB_Relations;



    DECLARE @childtable varchar(50);
    DECLARE @fkcolumn varchar(50);
    DECLARE @parenttable varchar(50);
    DECLARE @pkcolumn varchar(50);

    
    WHILE EXISTS (SELECT 1 FROM #LF_DB_Relations)
    BEGIN

        SELECT TOP 1 @parenttable = parenttable
        FROM #LF_DB_Relations;

        SELECT 'CREATE TRIGGER '+ 'Delete_' + MAX(parenttable) + '_Purging' + CHAR(10) +
           ' ON ' + MAX(parenttable) + CHAR(10) +
           ' INSTEAD OF DELETE ' + CHAR(10) +
            'AS ' + CHAR(10) +
            'BEGIN ' + CHAR(10) +
             'SET NOCOUNT ON; '+ CHAR(10) +

             STRING_AGG(CAST('DELETE FROM ' + childtable + ' WHERE '  + fkcolumn + ' IN (SELECT ' +  pkcolumn + ' FROM DELETED);'  AS NVARCHAR(MAX)), CHAR(10)) + 
              STRING_AGG(CAST('DELETE FROM ' + parenttable + ' WHERE '  + pkcolumn  + ' IN (SELECT ' +  pkcolumn + ' FROM DELETED);'  AS NVARCHAR(MAX)), CHAR(10)) + 

           
            'END' 
        FROM #LF_DB_Relations
        WHERE parenttable = @parenttable;

    
        DELETE FROM #LF_DB_Relations
        WHERE parenttable = @parenttable;

    END;

您需要处理每个父表一次,并在其触发器中删除所有

child
行。

请注意,以上代码尚未准备好用于生产。你需要:

  • 在表名和列名周围使用QUOTENAME
  • 可以为每个表添加架构
  • 您可能需要也可能不需要使用
    STRING_AGG
    用于父表
    DELETE
    语句(例如,如果父表中的多个列用作子表的外键)
  • 您可能在子表中具有外键层次结构 - 对于特定父表的图像,您需要从两个子表中删除数据,但其中一个子表具有 FK 到另一个 - 在这种情况下,删除顺序很重要;
  • 构建动态T-SQL触发器定义时,使用CHAR(10)、CHAR(13)等格式化代码;如果您构建一个行定义,那么编写触发器脚本的人将很难调试或更改它以阅读它;
© www.soinside.com 2019 - 2024. All rights reserved.