我正在尝试删除用户的数据以及位于不同表中的所有相关数据。所有表都有外键但没有级联删除。
我调查了一些选项:
- 在所有FK上启用级联删除,删除并移除级联删除。
- 从底部向上删除,循环所有叶子删除并重复此操作直到Root。
有没有更聪明的选择或其他技术?
我正在使用 Microsoft SQL Server 2012 (SP1)
我正在尝试删除用户的数据以及位于不同表中的所有相关数据。所有表都有外键但没有级联删除。
我调查了一些选项:
有没有更聪明的选择或其他技术?
我正在使用 Microsoft SQL Server 2012 (SP1)
这些是最好和最有效的。对于生产查询,我会使用2
.
我能想到的唯一其他方法(IMO)仅适用于在测试环境中快速而肮脏地删除数据(避免需要分析正确的顺序)
WITH CHECK
以避免使 FK 处于不受信任的状态,这意味着需要重新验证所有保留的数据。DELETE
语句,并根据需要多次运行批处理,直到它成功且没有 FK 错误。级联删除简单、性能良好且可靠。适用于多个级别。查询计划很有趣,而且看起来优化得很好。
如果您想要手动删除,请确保出于效率原因对每个表只发出一个查询。您可以在删除语句中使用连接来连接到父级别以过滤要删除的行。
我在下面找到了一个很好的解决方案: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'''
对于任何来这里寻找涉及同一表中的分层父/子关系的递归删除的人,这里有一个如何使用 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
我相信有可能做到
DELETE FROM tbl1
INNER JOIN tbl2
ON tbl1.ID = tbl2.tbl1ID
WHERE
tbl1.somthing = x