Создать оператор удаления из отношений внешнего ключа в SQL 2008? - PullRequest
18 голосов
/ 28 января 2009

Возможно ли с помощью скрипта / инструмента сгенерировать оператор удаления на основе таблиц fk отношений.

т.е. У меня есть таблица: DelMe (ID), и есть 30 таблиц с ссылками fk на его ID, которые мне нужно сначала удалить, есть ли какой-нибудь инструмент / скрипт, который я могу запустить, который сгенерирует 30 операторов delete на основе отношений FK я?

(кстати, я знаю о каскадном удалении отношений, я не могу использовать его в этой существующей базе данных)

Я использую Microsoft SQL Server 2008

Ответы [ 5 ]

18 голосов
/ 24 сентября 2012

Вот скрипт для каскадного удаления Аасим Абдулла , у меня работает на 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;

Пример использования 1

Обратите внимание на использование полного имени столбца в примере. Это тонко, но вы должны указать имя таблицы, чтобы сгенерированный SQL работал правильно.

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

Пример использования 2

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

Пример использования 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)'
15 голосов
/ 28 января 2009

Операторы DELETE, сгенерированные для использования в SP с параметром и как триггеры ON DELETE: (этот вариант поддерживает только один столбец FK)

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')
4 голосов
/ 28 января 2009

Я почти уверен, что разместил здесь код в Stack Overflow, который делает это автоматически, используя INFORMATION_SCHEMA для генерации динамического SQL, но я не могу его найти. Дай мне посмотреть, смогу ли я его восстановить.

Возможно, вам придется немного проверить это, я не смог найти свой исходный код, поэтому я изменил какой-то код, который у меня был, который автоматически создает плоские представления для звездных схем.

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
1 голос
/ 28 января 2009

Другой метод заключается в использовании генератора кода для создания Sql. Я почти уверен, что MyGeneration (без подключения) имеет существующие шаблоны для этого. Используя этот инструмент и правильный шаблон, вы можете создать сценарий SQL, который удаляет соответствующие вещи безболезненно.

0 голосов
/ 28 января 2009

К сожалению, я думаю, что каскадирование - это инструмент, который вы запрашиваете. Я понимаю, что не могу использовать его, но тот факт, что он существует как встроенная часть БД, в значительной степени убил необходимость в альтернативе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...