【问题标题】:Sql server - recursive deleteSql server - 递归删除
【发布时间】:2013-07-25 04:31:28
【问题描述】:

我正在尝试删除位于不同表中的用户数据及其所有相关数据。 所有表都有外键但没有级联删除。

我调查了一些选项:

  1. 在所有 FK 上启用级联删除,删除并移除级联删除。
  2. 从底部向上删除,循环向上删除所有叶子并重复此操作直到 Root。

还有更聪明的选择或其他技巧吗?

我使用的是 Microsoft SQL Server 2012 (SP1)

【问题讨论】:

    标签: sql tsql sql-server-2012


    【解决方案1】:

    那些是最好和最有效的。对于生产查询,我会使用2

    我能想到的唯一其他方法(IMO)仅适用于在测试环境中快速而肮脏地删除数据(避免需要分析正确的顺序)

    1. 禁用所有 FK 删除所需数据,然后重新启用 FK。这是低效的,因为它们需要重新启用 WITH CHECK 以避免使 FK 处于不受信任的状态,这意味着需要重新验证所有保留的数据。
    2. 以任意顺序列出受影响表上的所有 DELETE 语句,并根据需要多次运行批处理,直到成功且没有 FK 错误。

    【讨论】:

      【解决方案2】:
      【解决方案3】:

      级联删除简单、性能好且可靠。适用于多个级别。查询计划很有趣,而且看起来优化得很好。

      如果您想要手动删除,请确保为每个表只发出一个查询以提高效率。您可以在删除语句中使用连接来连接到父级别以过滤要删除的行。

      【讨论】:

        【解决方案4】:

        我找到了一个很好的解决方案, 下:How to generate DELETE statements in PL/SQL, based on the tables FK relations?

        生成函数:

        IF OBJECT_ID('dbo.udfGetFullQualName') IS NOT NULL
            DROP FUNCTION dbo.udfGetFullQualName;
        
        GO
        CREATE FUNCTION dbo.udfGetFullQualName
        (@ObjectId INT)
        RETURNS VARCHAR (300)
        AS
        BEGIN
            DECLARE @schema_id AS BIGINT;
            SELECT @schema_id = schema_id
            FROM   sys.tables
            WHERE  object_id = @ObjectId;
            RETURN '[' + SCHEMA_NAME(@schema_id) + '].[' + OBJECT_NAME(@ObjectId) + ']';
        END
        
        GO
        --============ Supporting Function dbo.udfGetOnJoinClause
        IF OBJECT_ID('dbo.udfGetOnJoinClause') IS NOT NULL
            DROP FUNCTION dbo.udfGetOnJoinClause;
        
        GO
        CREATE FUNCTION dbo.udfGetOnJoinClause
        (@fkNameId INT)
        RETURNS VARCHAR (1000)
        AS
        BEGIN
            DECLARE @OnClauseTemplate AS VARCHAR (1000);
            SET @OnClauseTemplate = '[<@pTable>].[<@pCol>] = [<@cTable>].[<@cCol>] AND ';
            DECLARE @str AS VARCHAR (1000);
            SET @str = '';
            SELECT @str = @str + REPLACE(REPLACE(REPLACE(REPLACE(@OnClauseTemplate, '<@pTable>', OBJECT_NAME(rkeyid)), '<@pCol>', COL_NAME(rkeyid, rkey)), '<@cTable>', OBJECT_NAME(fkeyid)), '<@cCol>', COL_NAME(fkeyid, fkey))
            FROM   dbo.sysforeignkeys AS fk
            WHERE  fk.constid = @fkNameId; --OBJECT_ID('FK_ProductArrearsMe_ProductArrears')
            RETURN LEFT(@str, LEN(@str) - LEN(' AND '));
        END
        
        GO
        --=========== CASECADE DELETE STORED PROCEDURE dbo.uspCascadeDelete
        IF OBJECT_ID('dbo.uspCascadeDelete') IS NOT NULL
            DROP PROCEDURE dbo.uspCascadeDelete;
        
        GO
        CREATE PROCEDURE dbo.uspCascadeDelete
        @ParentTableId VARCHAR (300), @WhereClause VARCHAR (2000), @ExecuteDelete CHAR (1)='N', --'N' IF YOU NEED DELETE SCRIPT
        @FromClause VARCHAR (8000)='', @Level INT=0 -- TABLE NAME OR OBJECT (TABLE) ID (Production.Location) WHERE CLAUSE (Location.LocationID = 7) 'Y' IF WANT TO DELETE DIRECTLY FROM SP,  IF LEVEL 0, THEN KEEP DEFAULT
        AS -- writen by Daniel Crowther 16 Dec 2004 - handles composite primary keys
        SET NOCOUNT ON;
        /* Set up debug */
        DECLARE @DebugMsg AS VARCHAR (4000), 
        @DebugIndent AS VARCHAR (50);
        SET @DebugIndent = REPLICATE('---', @@NESTLEVEL) + '> ';
        IF ISNUMERIC(@ParentTableId) = 0
            BEGIN -- assume owner is dbo and calculate id
                IF CHARINDEX('.', @ParentTableId) = 0
                    SET @ParentTableId = OBJECT_ID('[dbo].[' + @ParentTableId + ']');
                ELSE
                    SET @ParentTableId = OBJECT_ID(@ParentTableId);
            END
        IF @Level = 0
            BEGIN
                PRINT @DebugIndent + ' **************************************************************************';
                PRINT @DebugIndent + ' *** Cascade delete ALL data from ' + dbo.udfGetFullQualName(@ParentTableId);
                IF @ExecuteDelete = 'Y'
                    PRINT @DebugIndent + ' *** @ExecuteDelete = Y *** deleting data...';
                ELSE
                    PRINT @DebugIndent + ' *** Cut and paste output into another window and execute ***';
            END
        DECLARE @CRLF AS CHAR (2);
        SET @CRLF = CHAR(13) + CHAR(10);
        DECLARE @strSQL AS VARCHAR (4000);
        IF @Level = 0
            SET @strSQL = 'SET NOCOUNT ON' + @CRLF;
        ELSE
            SET @strSQL = '';
        SET @strSQL = @strSQL + 'PRINT ''' + @DebugIndent + dbo.udfGetFullQualName(@ParentTableId) + ' Level=' + CAST (@@NESTLEVEL AS VARCHAR) + '''';
        IF @ExecuteDelete = 'Y'
            EXECUTE (@strSQL);
        ELSE
            PRINT @strSQL;
        DECLARE curs_children CURSOR LOCAL FORWARD_ONLY
            FOR SELECT DISTINCT constid AS fkNameId, -- constraint name
                                fkeyid AS cTableId
                FROM   dbo.sysforeignkeys AS fk
                WHERE  fk.rkeyid <> fk.fkeyid -- WE DO NOT HANDLE self referencing tables!!!
                       AND fk.rkeyid = @ParentTableId;
        OPEN curs_children;
        DECLARE @fkNameId AS INT, 
        @cTableId AS INT, 
        @cColId AS INT, 
        @pTableId AS INT, 
        @pColId AS INT;
        FETCH NEXT FROM curs_children INTO @fkNameId, @cTableId; --, @cColId, @pTableId, @pColId
        DECLARE @strFromClause AS VARCHAR (1000);
        DECLARE @nLevel AS INT;
        IF @Level = 0
            BEGIN
                SET @FromClause = 'FROM ' + dbo.udfGetFullQualName(@ParentTableId);
            END
        WHILE @@FETCH_STATUS = 0
            BEGIN
                SELECT @strFromClause = @FromClause + @CRLF + '      INNER JOIN ' + dbo.udfGetFullQualName(@cTableId) + @CRLF + '       ON ' + dbo.udfGetOnJoinClause(@fkNameId);
                SET @nLevel = @Level + 1;
                EXECUTE dbo.uspCascadeDelete @ParentTableId = @cTableId, @WhereClause = @WhereClause, @ExecuteDelete = @ExecuteDelete, @FromClause = @strFromClause, @Level = @nLevel;
                SET @strSQL = 'DELETE FROM ' + dbo.udfGetFullQualName(@cTableId) + @CRLF + @strFromClause + @CRLF + 'WHERE   ' + @WhereClause + @CRLF;
                SET @strSQL = @strSQL + 'PRINT ''---' + @DebugIndent + 'DELETE FROM ' + dbo.udfGetFullQualName(@cTableId) + '     Rows Deleted: '' + CAST(@@ROWCOUNT AS VARCHAR)' + @CRLF + @CRLF;
                IF @ExecuteDelete = 'Y'
                    EXECUTE (@strSQL);
                ELSE
                    PRINT @strSQL;
                FETCH NEXT FROM curs_children INTO @fkNameId, @cTableId;
            --, @cColId, @pTableId, @pColId
            END
        IF @Level = 0
            BEGIN
                SET @strSQL = @CRLF + 'PRINT ''' + @DebugIndent + dbo.udfGetFullQualName(@ParentTableId) + ' Level=' + CAST (@@NESTLEVEL AS VARCHAR) + ' TOP LEVEL PARENT TABLE''' + @CRLF;
                SET @strSQL = @strSQL + 'DELETE FROM ' + dbo.udfGetFullQualName(@ParentTableId) + ' WHERE ' + @WhereClause + @CRLF;
                SET @strSQL = @strSQL + 'PRINT ''' + @DebugIndent + 'DELETE FROM ' + dbo.udfGetFullQualName(@ParentTableId) + ' Rows Deleted: '' + CAST(@@ROWCOUNT AS VARCHAR)' + @CRLF;
                IF @ExecuteDelete = 'Y'
                    EXECUTE (@strSQL);
                ELSE
                    PRINT @strSQL;
            END
        CLOSE curs_children;
        DEALLOCATE curs_children;
        

        用法:

        EXEC uspCascadeDelete
        @ParentTableId = '[VAULT].[Package]',
        @WhereClause = 'CreatedBy  =''VZBMNSTEST'''
        

        【讨论】:

        • 在将 PL/SQL 解决方案应用于 MSSQL/T-SQL 时要小心。我看不出您的代码有任何问题,但是 Oracle 讨厌您,并且会为了恶意而采取不同的做法。
        【解决方案5】:

        对于任何来这里寻找涉及同一表中的分层父/子关系的递归删除的人,这里有一个如何使用 CTE 执行此操作的示例:

        declare @headid int
        
        ;with RowsToDelete as (
            SELECT head.id
            FROM ..hierarchicaltable head
            WHERE id = @headid
        
            UNION ALL
        
            SELECT child.id
            FROM ..hierarchicaltable child
            INNER JOIN RowsToDelete parent
                ON parent.id = child.parentid
        )
        delete ht
        FROM ..hierarchicaltable ht
        INNER JOIN RowsToDelete d
            ON ht.id = d.id
        

        【讨论】:

        • 经过测试并且在 SQL Server 中运行良好。我对.. 感到困惑,但只需将..hierarchicaltable 切换为[TableName]。传奇!
        【解决方案6】:

        我相信这是可能的

        DELETE FROM tbl1
        INNER JOIN tbl2
        ON tbl1.ID = tbl2.tbl1ID
        WHERE
        tbl1.somthing = x
        

        【讨论】:

          猜你喜欢
          • 2014-09-05
          • 1970-01-01
          • 2014-08-23
          • 1970-01-01
          • 1970-01-01
          • 2014-12-19
          • 2011-01-10
          • 2017-02-02
          • 2012-12-05
          相关资源
          最近更新 更多