Gerar Declaração DELETE FROM relações de chave estrangeira no SQL 2008?
-
20-08-2019 - |
Pergunta
É possível via script / ferramenta para gerar uma instrução de exclusão baseada em relações as tabelas fk.
i. Tenho a tabela: delme (ID) e existem 30 mesas com referências fk ao seu ID que eu preciso para eliminar em primeiro lugar, existe alguma ferramenta / script que eu possa executar que irá gerar as 30 declarações de exclusão com base nas relações FK para me?
(btw eu sei sobre exclusão em cascata sobre as relações, eu não posso usá-lo neste db existente)
Estou usando o Microsoft SQL Server 2008
Solução
instruções DELETE geradas para uso em SP com o parâmetro, e como ON gatilhos de DELETE: (Esta variante suporta apenas FKS coluna única)
SELECT 'DELETE '+detail.name+' WHERE '+dcolumn.name+' = @'+mcolumn.name AS stmt,
'DELETE ' + detail.name + ' FROM ' + detail.name + ' INNER JOIN deleted ON ' +
detail.name + '.' + dcolumn.name + ' = deleted.' + mcolumn.name AS trg
FROM sys.columns AS mcolumn
INNER JOIN sys.foreign_key_columns ON mcolumn.object_id =
sys.foreign_key_columns.referenced_object_id
AND mcolumn.column_id = sys.foreign_key_columns.referenced_column_id
INNER JOIN sys.tables AS master ON mcolumn.object_id = master.object_id
INNER JOIN sys.columns AS dcolumn
ON sys.foreign_key_columns.parent_object_id = dcolumn.object_id
AND sys.foreign_key_columns.parent_column_id = dcolumn.column_id
INNER JOIN sys.tables AS detail ON dcolumn.object_id = detail.object_id
WHERE (master.name = N'MyTableName')
Outras dicas
Aqui está um script para eliminação em cascata por Aasim Abdullah , funciona para mim no MS SQL Server 2008:
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;
Exemplo de uso 1
Observe o uso do nome da coluna totalmente qualificado no exemplo. É sutil, mas você deve especificar o nome da tabela para o SQL gerado para executar corretamente.
EXEC uspCascadeDelete
@ParentTableId = 'Production.Location',
@WhereClause = 'Location.LocationID = 2'
Exemplo de uso 2
EXEC uspCascadeDelete
@ParentTableId = 'dbo.brand',
@WhereClause = 'brand.brand_name <> ''Apple'''
Exemplo de uso 3
exec uspCascadeDelete
@ParentTableId = 'dbo.product_type',
@WhereClause = 'product_type.product_type_id NOT IN
(SELECT bpt.product_type_id FROM dbo.brand_product_type bpt)'
Eu tenho certeza que eu postei código aqui no estouro de pilha que faz isso automaticamente usando INFORMATION_SCHEMA
para gerar SQL dinâmico, mas eu não posso encontrá-lo. Deixe-me ver se eu posso regenerá-lo.
Você pode precisar verificar isso um pouco, eu não poderia encontrar meu código original, então eu modificado algum código que eu tinha que constrói vistas flattend para star-esquemas automaticamente.
DECLARE @COLUMN_NAME AS sysname
DECLARE @TABLE_NAME AS sysname
DECLARE @IDValue AS int
SET @COLUMN_NAME = '<Your COLUMN_NAME here>'
SET @TABLE_NAME = '<Your TABLE_NAME here>'
SET @IDValue = 123456789
DECLARE @sql AS varchar(max) ;
WITH RELATED_COLUMNS
AS (
SELECT QUOTENAME(c.TABLE_SCHEMA) + '.'
+ QUOTENAME(c.TABLE_NAME) AS [OBJECT_NAME]
,c.COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS AS c WITH (NOLOCK)
INNER JOIN INFORMATION_SCHEMA.TABLES AS t WITH (NOLOCK)
ON c.TABLE_CATALOG = t.TABLE_CATALOG
AND c.TABLE_SCHEMA = t.TABLE_SCHEMA
AND c.TABLE_NAME = t.TABLE_NAME
AND t.TABLE_TYPE = 'BASE TABLE'
INNER JOIN (
SELECT rc.CONSTRAINT_CATALOG
,rc.CONSTRAINT_SCHEMA
,lkc.TABLE_NAME
,lkc.COLUMN_NAME
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
WITH (NOLOCK)
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE lkc
WITH (NOLOCK)
ON lkc.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
AND lkc.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
AND lkc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
WITH (NOLOCK)
ON rc.CONSTRAINT_CATALOG = tc.CONSTRAINT_CATALOG
AND rc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
AND rc.UNIQUE_CONSTRAINT_NAME = tc.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE rkc
WITH (NOLOCK)
ON rkc.CONSTRAINT_CATALOG = tc.CONSTRAINT_CATALOG
AND rkc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
AND rkc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
WHERE rkc.COLUMN_NAME = @COLUMN_NAME
AND rkc.TABLE_NAME = @TABLE_NAME
) AS j
ON j.CONSTRAINT_CATALOG = c.TABLE_CATALOG
AND j.CONSTRAINT_SCHEMA = c.TABLE_SCHEMA
AND j.TABLE_NAME = c.TABLE_NAME
AND j.COLUMN_NAME = c.COLUMN_NAME
)
SELECT @sql = COALESCE(@sql, '') + 'DELETE FROM ' + [OBJECT_NAME]
+ ' WHERE ' + [COLUMN_NAME] + ' = ' + CONVERT(varchar, @IDValue)
+ CHAR(13) + CHAR(10)
FROM RELATED_COLUMNS
PRINT @sql
Outra técnica é usar um gerador de código para criar o Sql. Eu tenho certeza que a MyGeneration (sem conexão) tem modelos existentes para fazer isso. Usando essa ferramenta e o modelo certo pode criar um script sql que exclui o material relevante com nenhuma dor.
Infelizmente, acho que em cascata é a ferramenta que você está pedindo. Eu não entendo ser capaz de usá-lo, mas o fato de que ele existe como um built-in parte do db tem praticamente matou a necessidade de uma alternativa.