4

現在、データベース内のテーブルの依存関係を見つけることに取り組んでいます。そして、データベース内のいくつかのテーブルの循環依存の問題に悩まされています。

一部のテーブルは循環的に依存しているため、注文全体を取得できません.....

データベース ダイアグラム以外に、SQL Server の任意のデータベースで循環従属テーブルを見つける方法はありますか??

4

4 に答える 4

10

私は、いくつかの場所で複製されていることがわかったスクリプトに出くわしました。元は、 SQL Azure チーム ブログの 2010 年の次の記事からのものだと思います。

リレーショナル データベースの世界では、循環参照は、テーブルを関連付ける外部キーがループを作成するスキーマ構造です。循環参照は、外部キーが強制されている 2 つのリレーショナル データベースを同期しようとすると、特別な種類の問題を引き起こします。この問題のため、循環参照を含むデータベース スキーマは、データベースの同期および複製時に使用できるツールで制限されます。この記事では、循環参照について説明し、データベースに循環参照があるかどうかを判断するための Transact-SQL スクリプトを示します。

ここにも複製され、Wayne Berry の功績が認められています。たぶん、彼は Sql Azure チームに所属していたのでしょうか?

@ Aaron_Bertrandの答えは素晴らしく簡潔です。完全を期すために、このスクリプトはより長い依存関係チェーンを検出するため、このスクリプトを追加する価値があると思います。リンクは、次の人が簡単に使えるようにするためのリンクの希望ではなく、ここでコードを再現することを見つけるのに十分困難でした.

簡潔ではありません。

以下の Transact-SQL スクリプトは、再帰カーソルを使用して、データベース スキーマに循環参照があるかどうかを検出します。SQL Azure との同期を試みる前に SQL Server データベースで実行することも、SQL Azure データベースで実行することもできます。SQL Server Management Studio のクエリ ウィンドウで実行できます。メッセージセクションのように出力が表示されます。

循環参照がある場合、出力は次のようになります。

dbo.City -> dbo.Author -> dbo.City dbo.Division -> dbo.Author -> dbo.City -> dbo.County -> dbo.Region -> dbo.Image -> dbo.Division dbo.State - > dbo.Image -> dbo.Area -> dbo.Author -> dbo.City -> dbo.County -> dbo.Region -> >dbo.State dbo.County -> dbo.Region -> dbo.Author -> dbo.City -> dbo.County dbo.Image -> dbo.Area -> dbo.Author -> dbo.City -> dbo.County -> dbo.Region -> dbo.Image dbo.Location -> dbo.Author - > dbo.City -> dbo.County -> dbo.Region -> dbo.Image -> dbo.Location dbo.LGroup -> dbo.LGroup dbo.Region -> dbo.Author -> dbo.City -> dbo.County -> dbo.Region dbo.Author -> dbo.City -> dbo.Author dbo.Area -> dbo.Author -> dbo.City -> dbo.County -> dbo.Region -> dbo.Image -> dbo.領域

各行は循環参照であり、円を作成するテーブルのリンク リストがあります。循環参照を検出する Transact-SQL スクリプトを以下に示します。このコードは、SQL Azure および SQL Server で動作します。

SET NOCOUNT ON

-- WWB: Create a Temp Table Of All Relationship To Improve Overall Performance
CREATE TABLE #TableRelationships (FK_Schema nvarchar(max), FK_Table nvarchar(max),
    PK_Schema nvarchar(max), PK_Table nvarchar(max))

-- WWB: Create a List Of All Tables To Check
CREATE TABLE #TableList ([Schema] nvarchar(max), [Table] nvarchar(max))

-- WWB: Fill the Table List
INSERT INTO #TableList ([Table], [Schema])
SELECT TABLE_NAME, TABLE_SCHEMA
FROM INFORMATION_SCHEMA.TABLES 
WHERE Table_Type = 'BASE TABLE'

-- WWB: Fill the RelationShip Temp Table
INSERT INTO #TableRelationships(FK_Schema, FK_Table, PK_Schema, PK_Table)
SELECT
    FK.TABLE_SCHEMA,
    FK.TABLE_NAME,
    PK.TABLE_SCHEMA,
    PK.TABLE_NAME
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C
      INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON 
        C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME
      INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON 
        C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME
      INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON 
        C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME
      INNER JOIN (
            SELECT i1.TABLE_NAME, i2.COLUMN_NAME
            FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1
            INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON
             i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME
            WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY'
) PT ON PT.TABLE_NAME = PK.TABLE_NAME

CREATE TABLE #Stack([Schema] nvarchar(max), [Table] nvarchar(max))

GO

-- WWB: Drop SqlAzureRecursiveFind
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = 
    OBJECT_ID(N'[dbo].[SqlAzureRecursiveFind]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[SqlAzureRecursiveFind]

GO

-- WWB: Create a Stored Procedure that Recursively Calls Itself
CREATE PROC SqlAzureRecursiveFind
    @BaseSchmea nvarchar(max),
    @BaseTable nvarchar(max),
    @Schmea nvarchar(max),
    @Table nvarchar(max),
    @Fail nvarchar(max) OUTPUT
AS

    SET NOCOUNT ON

    -- WWB: Keep Track Of the Schema and Tables We Have Checked
    -- Prevents Looping          
    INSERT INTO #Stack([Schema],[Table]) VALUES (@Schmea, @Table)

    DECLARE @RelatedSchema nvarchar(max)
    DECLARE @RelatedTable nvarchar(max)

    -- WWB: Select all tables that the input table is dependent on
    DECLARE table_cursor CURSOR LOCAL  FOR
          SELECT PK_Schema, PK_Table
          FROM #TableRelationships
          WHERE FK_Schema = @Schmea AND FK_Table = @Table

    OPEN table_cursor;

    -- Perform the first fetch.
    FETCH NEXT FROM table_cursor INTO @RelatedSchema, @RelatedTable;

    -- Check @@FETCH_STATUS to see if there are any more rows to fetch.
    WHILE @@FETCH_STATUS = 0
    BEGIN

        -- WWB: If We have Recurred To Where We Start This
        -- Is a Circular Reference
        -- Begin failing out of the recursions
        IF (@BaseSchmea = @RelatedSchema AND 
                @BaseTable = @RelatedTable)
            BEGIN
                SET @Fail = @RelatedSchema + '.' + @RelatedTable
                RETURN
            END
        ELSE            
        BEGIN

            DECLARE @Count int

            -- WWB: Check to make sure that the dependencies are not in the stack
            -- If they are we don't need to go down this branch
            SELECT    @Count = COUNT(1)
            FROM    #Stack    
            WHERE    #Stack.[Schema] = @RelatedSchema AND 
                #Stack.[Table] = @RelatedTable

            IF (@Count=0) 
            BEGIN
                -- WWB: Recurse
                EXECUTE SqlAzureRecursiveFind @BaseSchmea, 
                    @BaseTable, 
                    @RelatedSchema, @RelatedTable, @Fail OUTPUT
                IF (LEN(@Fail) > 0)
                BEGIN
                    -- WWB: If the Call Fails, Build the Output Up
                    SET @Fail = @RelatedSchema + '.' + @RelatedTable 
                        + ' -> ' + @Fail
                    RETURN
                END
            END
       END

       -- This is executed as long as the previous fetch succeeds.
    FETCH NEXT FROM table_cursor INTO @RelatedSchema, @RelatedTable;
    END

    CLOSE table_cursor;
    DEALLOCATE table_cursor;    

GO    

SET NOCOUNT ON

DECLARE @Schema nvarchar(max)
DECLARE @Table nvarchar(max)
DECLARE @Fail nvarchar(max)

-- WWB: Loop Through All the Tables In the Database Checking Each One
DECLARE list_cursor CURSOR FOR
      SELECT [Schema], [Table]
      FROM #TableList

OPEN list_cursor;

-- Perform the first fetch.
FETCH NEXT FROM list_cursor INTO @Schema, @Table;

-- Check @@FETCH_STATUS to see if there are any more rows to fetch.
WHILE @@FETCH_STATUS = 0
BEGIN

    -- WWB: Clear the Stack (Don't you love Global Variables)
    DELETE #Stack

    -- WWB: Initialize the Input
    SET @Fail = ''

    -- WWB: Check the Table
    EXECUTE SqlAzureRecursiveFind @Schema, 
        @Table, @Schema,
         @Table, @Fail OUTPUT
    IF (LEN(@Fail) > 0)
    BEGIN
        -- WWB: Failed, Output
        SET @Fail = @Schema + '.' + @Table + ' -> ' + @Fail
        PRINT @Fail
    END

   -- This is executed as long as the previous fetch succeeds.
    FETCH NEXT FROM list_cursor INTO @Schema, @Table;
END

-- WWB: Clean Up
CLOSE list_cursor;
DEALLOCATE list_cursor;    

DROP TABLE #TableRelationships
DROP TABLE #Stack
DROP TABLE #TableList
DROP PROC SqlAzureRecursiveFind
于 2014-10-13T03:15:32.963 に答える
0

回答1の SQL Azure チーム ブログのスクリプトで、 自己参照 (同じテーブルに対する親子関係) のみが表示されます。だから私は自分のスクリプトを書きました:

-- variables for the path output
declare @delimList nvarchar(max) = ' > ',
        @delimDot nvarchar(max) = '.'

/* Part 1: read all fk-pk relation
does not perform well in SQL Server with a CTE, thus using a temp table */
create table #fk_pk(
    PK_schema sysname not null,
    PK_table sysname not null,
    FK_schema sysname not null,
    FK_table sysname not null
)

insert into #fk_pk(
    PK_schema,
    PK_table,
    FK_schema,
    FK_table
)
select      distinct
            PK.TABLE_SCHEMA PK_schema,
            PK.TABLE_NAME PK_table,
            FK.TABLE_SCHEMA FK_schema,
            FK.TABLE_NAME FK_table
from        INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK
            inner join
            INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C
            on C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME
            inner join
            INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK
            on C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME
where       PK.CONSTRAINT_TYPE = 'PRIMARY KEY'
            and
            -- ignore self-references
            not (
                PK.TABLE_SCHEMA = FK.TABLE_SCHEMA
                and
                PK.TABLE_NAME = FK.TABLE_NAME
            )

;
with relation(
    sourceSchema,
    sourceTable,
    PK_schema,
    PK_table,
    FK_schema,
    FK_table,
    path
) as (
    /* Part 2: Find PKs that are referenced more then once (reduces workload for next step) */
    -- anchor: more then one fk reference these pk tables
    select      fk_pk.PK_schema sourceSchema,
                fk_pk.PK_table sourceTable,
                fk_pk.PK_schema,
                fk_pk.PK_table,
                fk_pk.FK_schema,
                fk_pk.FK_table,
                cast(fk_pk.PK_schema as nvarchar(max)) + @delimDot + fk_pk.PK_table + @delimList + fk_pk.FK_schema + @delimDot +  fk_pk.FK_table path
    from        #fk_pk fk_pk
    where       exists(
                    select      1
                    from        #fk_pk fk_pk_exists
                    where       fk_pk_exists.PK_schema = fk_pk.PK_schema
                                and
                                fk_pk_exists.PK_table = fk_pk.PK_table
                                and
                                not (
                                    fk_pk_exists.FK_schema = fk_pk.FK_schema
                                    and
                                    fk_pk_exists.FK_table = fk_pk.FK_table
                                )
                )

    /* Part 3: Find all possible paths from those PK tables to any other table (using recursive CTE) */
    union all

    -- recursive
    select      relation.sourceSchema,
                relation.sourceTable,
                fk_pk_child.PK_schema,
                fk_pk_child.PK_table,
                fk_pk_child.FK_schema,
                fk_pk_child.FK_table,
                /* Part 5: Display result nicely
                compose a path like: A -> B -> C */
                relation.path + @delimList + fk_pk_child.FK_schema + @delimDot + fk_pk_child.FK_table path
    from        #fk_pk fk_pk_child
                inner join
                relation
                on  relation.FK_schema = fk_pk_child.PK_schema
                    and
                    relation.FK_table = fk_pk_child.PK_table
)

/* Part 4: Identify problematic circles */
select      relation.sourceSchema + @delimDot + relation.sourceTable source,
            relation.FK_schema + @delimDot + relation.FK_table target,
            relation.path
from        relation
where       exists(
                select      1
                from        relation relation_exists
                where       relation_exists.sourceSchema = relation.sourceSchema
                            and
                            relation_exists.sourceTable = relation.sourceTable
                            and
                            not (
                                relation_exists.PK_schema = relation.PK_schema
                                and
                                relation_exists.PK_table = relation.PK_table
                            )
                            and
                            relation_exists.FK_schema = relation.FK_schema
                            and
                            relation_exists.FK_table = relation.FK_table

            )
order by    relation.sourceSchema,
            relation.sourceTable,
            relation.FK_schema,
            relation.FK_table,
            relation.path

drop table #fk_pk
go

循環参照と円を形成するパスを報告します。

このスクリプトは SQL Server 専用で、コードの説明とともにgithub リポジトリでホストされています。他のRDBMSに移植する場合はお知らせください。

于 2017-09-10T20:14:03.303 に答える