Pregunta

¿Es posible mediante un script / herramienta generar una declaración de eliminación basada en las relaciones fk de las tablas?

es decir Tengo la tabla: DelMe (ID) y hay 30 tablas con referencias fk a su ID que necesito eliminar primero, ¿hay alguna herramienta / script que pueda ejecutar que generará las 30 declaraciones de eliminación basadas en las relaciones FK para yo?

(por cierto, sé sobre la eliminación en cascada en las relaciones, no puedo usarlo en este db existente)

Estoy usando Microsoft SQL Server 2008

¿Fue útil?

Solución

Sentencias DELETE generadas para su uso en SP con parámetro, y como desencadenantes ON DELETE: (esta variante solo admite FK de columna ú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')

Otros consejos

Aquí hay un script para eliminar en cascada por Aasim Abdullah , funciona para mí en 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;

Ejemplo de uso 1

Tenga en cuenta el uso del nombre de columna completo en el ejemplo. Es sutil, pero debe especificar el nombre de la tabla para que el SQL generado se ejecute correctamente.

EXEC uspCascadeDelete
@ParentTableId = 'Production.Location',
@WhereClause = 'Location.LocationID = 2'

Ejemplo de uso 2

EXEC uspCascadeDelete
@ParentTableId = 'dbo.brand',
@WhereClause = 'brand.brand_name <> ''Apple'''

Ejemplo 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)'

Estoy bastante seguro de que publiqué el código aquí en Stack Overflow que hace esto automáticamente usando INFORMATION_SCHEMA para generar SQL dinámico, pero no puedo encontrarlo. Déjame ver si puedo regenerarlo.

Es posible que deba verificar esto un poco, no pude encontrar mi código original, por lo que modifiqué un código que tenía que crea vistas planas para esquemas de estrellas automáticamente.

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

Otra técnica es usar un generador de código para crear el SQL. Estoy bastante seguro de que MyGeneration (sin conexión) tiene plantillas existentes para hacerlo. Usando esa herramienta y la plantilla correcta, puede crear un script sql que elimine las cosas relevantes sin dolor.

Desafortunadamente, creo que en cascada es la herramienta que está solicitando. Entiendo que no puedo usarlo, pero el hecho de que exista como parte incorporada de la base de datos prácticamente ha eliminado la necesidad de una alternativa.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top