SQL Server 中时态表的自定义级联删除存储过程

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

前言:

我想在SQL Server(2016 - v13.0)中创建一个存储过程,以手动跨具有外键约束的表(包括自引用表)执行级联删除。由于使用时态表,我需要自定义实现,该时态表不支持级联删除。我发现这个限制非常令人沮丧,特别是考虑到临时表是为了跟踪删除而设计的。为了删除一行,必须首先删除依赖于它的所有行。

我应该清楚:我不想触摸历史表中的数据,我只关心删除当前表中的行。

我对 SQL 的了解不是很深,我不确定这是否是最好的方法。我考虑过使用软删除,但我不喜欢它们,因为它们会带来混乱并且需要勤奋地维护数据准确性。我听说其他人建议使用存储过程来显式执行删除,但在我的场景中,我有太多表,无法为每个表单独实现删除过程;相反,我需要一个适用于任何给定表行的动态表。

目标:

存储过程应该:

  • 接受三个参数:
    @SchemaName
    (架构名称)、
    @TableName
    (表名称)和
    @Id
    (要删除的实体的主键)。
  • 删除指定的行以及依赖于它的任何行,尊重所有外键约束。
  • 通过在父行之前删除子行来正确处理自引用表。这可能就是事情冒险进入可怕领域的地方。
  • 在单笔交易中进行操作。

示例场景:

考虑一下我正在使用的数据库的超简化表示。

DBML diagram of example database schema

期望:

鉴于此层次结构,如果我尝试删除

Folder
,该过程应递归删除所有依赖的
ElementInstances
Elements
Pages
Forms
和子
Folders
(按顺序)。

问题:

  • 实现一个存储过程来处理这种时态表的动态级联删除操作是否可行?
  • 如何有效处理递归删除,尤其是自引用表,而不遇到递归深度问题?

任何指导或示例将不胜感激!

sql-server stored-procedures database-design foreign-keys cascading-deletes
1个回答
0
投票

我决定为每个表编写一个删除程序。下面是我正在构建的应用程序的一个示例,与我的 OP 中给出的示例不是 1:1,但它遵循相同的原则。

以下是删除具有自引用的顶级实体的过程。它将首先找到其所有子文件夹并对其执行

DeleteFolder
过程以开始某种递归过程,然后它将通过
DeleteForm
过程删除属于它们的表单。

DeleteFolder.sql

CREATE PROCEDURE [dbo].[DeleteFolder]
    @FolderId INT
AS
BEGIN
    SET NOCOUNT ON;

    -- Delete child folders
    DECLARE @ChildFolderId INT;
    DECLARE ChildFoldersCursor CURSOR FOR
    SELECT [Id] FROM [ui].[Folders] WHERE [ParentId] = @FolderId;

    OPEN ChildFoldersCursor;
    FETCH NEXT FROM ChildFoldersCursor INTO @ChildFolderId;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXEC [dbo].[DeleteFolder] @ChildFolderId;
        FETCH NEXT FROM ChildFoldersCursor INTO @ChildFolderId;
    END

    CLOSE ChildFoldersCursor;
    DEALLOCATE ChildFoldersCursor;

    -- Delete related forms
    DECLARE @FormId INT;
    DECLARE FormsCursor CURSOR FOR
    SELECT [Id] FROM [config].[Forms] WHERE [FolderId] = @FolderId;

    OPEN FormsCursor;
    FETCH NEXT FROM FormsCursor INTO @FormId;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXEC [dbo].[DeleteForm] @FormId;
        FETCH NEXT FROM FormsCursor INTO @FormId;
    END

    CLOSE FormsCursor;
    DEALLOCATE FormsCursor;

    -- Delete the folder
    DELETE FROM [ui].[Folders] WHERE [Id] = @FolderId;
END

这是上一个中调用的过程。它详细说明了如何删除依赖于给定表单的实体。同样,它继续使用存储过程进行删除,而不是显式行删除。

DeleteForm.sql

CREATE PROCEDURE [dbo].[DeleteForm]
    @FormId INT
AS
BEGIN
    SET NOCOUNT ON;

    -- Delete related pages
    DECLARE @PageId INT;
    DECLARE PagesCursor CURSOR FOR
    SELECT [Id] FROM [ui].[Pages] WHERE [FormId] = @FormId;

    OPEN PagesCursor;
    FETCH NEXT FROM PagesCursor INTO @PageId;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXEC [dbo].[DeletePage] @PageId;
        FETCH NEXT FROM PagesCursor INTO @PageId;
    END

    CLOSE PagesCursor;
    DEALLOCATE PagesCursor;

    -- Delete related lookup lists
    DECLARE @LookupListId INT;
    DECLARE LookupListsCursor CURSOR FOR
    SELECT [Id] FROM [config].[LookupLists] WHERE [FormId] = @FormId;

    OPEN LookupListsCursor;
    FETCH NEXT FROM LookupListsCursor INTO @LookupListId;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXEC [dbo].[DeleteLookupList] @LookupListId;
        FETCH NEXT FROM LookupListsCursor INTO @LookupListId;
    END

    CLOSE LookupListsCursor;
    DEALLOCATE LookupListsCursor;

    -- Delete the related Elements
    DECLARE @ElementId INT;
    DECLARE ElementsCursor CURSOR FOR
    SELECT [Id] FROM [config].[Elements] WHERE [FormId] = @FormId;

    OPEN ElementsCursor;
    FETCH NEXT FROM ElementsCursor INTO @ElementId;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXEC [dbo].[DeleteElement] @ElementId;
        FETCH NEXT FROM ElementsCursor INTO @ElementId;
    END

    CLOSE ElementsCursor;
    DEALLOCATE ElementsCursor;

    -- Delete the form
    DELETE FROM [config].[Forms] WHERE [Id] = @FormId;
END

最终,我们会删除不依赖于任何东西的叶子,因此我们模拟了

ON DELETE CASCADE
,具有更多的灵活性和(不幸的是)更多的代码。

© www.soinside.com 2019 - 2024. All rights reserved.