从 SQL 2008 中的外键关系生成删除语句?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/485581/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-01 00:54:49  来源:igfitidea点击:

Generate Delete Statement From Foreign Key Relationships in SQL 2008?

sqlcode-generationforeign-keysdynamic-sql

提问by Element

Is it possible via script/tool to generate a delete statement based on the tables fk relations.

是否可以通过脚本/工具根据表 fk 关系生成删除语句。

i.e. I have the table: DelMe(ID) and there are 30 tables with fk references to its ID that I need to delete first, is there some tool/script that I can run that will generate the 30 delete statements based on the FK relations for me ?

即我有表:DelMe(ID) 并且有 30 个表带有对其 ID 的 fk 引用,我需要先删除,是否有一些我可以运行的工具/脚本可以根据 FK 关系生成 30 个删除语句为了我 ?

(btw I know about cascade delete on the relations, I can't use it in this existing db)

(顺便说一句,我知道关系上的级联删除,我不能在这个现有的数据库中使用它)

I'm using Microsoft SQL Server 2008

我使用的是 Microsoft SQL Server 2008

采纳答案by devio

DELETE statements generated for use in SP with parameter, and as ON DELETE triggers: (this variant supports single column FKs only)

生成的 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')

回答by Ohad Schneider

Here is a script for cascading delete by Aasim Abdullah, works for me on MS SQL Server 2008:

这是Aasim Abdullah 的级联删除脚本,适用于 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;

Usage example 1

使用示例 1

Note the use of the fully qualified column name in the example. It's subtle but you must specify the table name for the generated SQL to execute properly.

请注意示例中完全限定列名的使用。这很微妙,但您必须为生成的 SQL 指定表名才能正确执行。

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

Usage example 2

使用示例 2

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

Usage example 3

使用示例 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)'

回答by Cade Roux

I'm pretty sure I posted code here on Stack Overflow which does this automatically using INFORMATION_SCHEMAto generate dynamic SQL, but I can't find it. Let me see if I can regenerate it.

我很确定我在 Stack Overflow 上发布了代码,它自动执行此操作INFORMATION_SCHEMA以生成动态 SQL,但我找不到它。让我看看我能不能再生它。

You might need to check this out a bit, I couldn't find my original code, so I modified some code I had which builds flattend views for star-schemas automatically.

你可能需要检查一下,我找不到我的原始代码,所以我修改了一些代码,这些代码自动为星型模式构建平面视图。

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

回答by MrTelly

Another technique is to use a code generator to create the Sql. I'm pretty sure the MyGeneration(no connection) has existing templates to do this. Using that tool and the right template you can create a sql script that deletes the relevant stuff with no pain.

另一种技术是使用代码生成器来创建 Sql。我很确定MyGeneration(无连接)有现有的模板来做到这一点。使用该工具和正确的模板,您可以创建一个 sql 脚本,该脚本可以毫不费力地删除相关内容。

回答by Joel Coehoorn

Unfortunately, I think cascading isthe tool you're asking for. I understand not being able to use it, but that fact that it exists as a built-in part of the db has pretty much killed the need for an alternative.

不幸的是,我认为级联您要求的工具。我知道无法使用它,但它作为数据库的内置部分存在这一事实几乎消除了对替代方案的需求。