2

目前正在努力尋找數據庫中表的依賴性順序。並且在數據庫中有一些表的循環依賴的問題。如何在sql服務器中查找循環依賴表

,因爲一些表循環依賴IM沒有得到整個訂單.....

有沒有辦法找到在SQL Server數據庫的任何依賴圓形表,除了數據庫圖表?

+0

你只是指外鍵引用?因爲可能存在其他依賴項,例如引用從另一個表中抽取的UDF的檢查約束等。 –

+0

是的,我的意思是外鍵引用! – Sooraj

回答

8

您並不需要購買工具來查找這些參考文獻。

SELECT 
    OBJECT_SCHEMA_NAME(fk1.parent_object_id) 
    + '.' + OBJECT_NAME(fk1.parent_object_id), 
    OBJECT_SCHEMA_NAME(fk2.parent_object_id) 
    + '.' + OBJECT_NAME(fk2.parent_object_id) 
FROM sys.foreign_keys AS fk1 
INNER JOIN sys.foreign_keys AS fk2 
ON fk1.parent_object_id = fk2.referenced_object_id 
AND fk2.parent_object_id = fk1.referenced_object_id; 
+0

@Sooraj,你想要一個能夠找到涉及73個表的循環引用的查詢,因爲這也是可能的嗎? –

+0

看起來它只能找到直接循環引用(A-> B和B-> A),而不是像(A-> B,B-> C和C->)這樣更隱蔽的鏈。 –

+0

@Karl yep,我從來沒有聲稱它會找到三,四或73張表(後者聲稱是來自OP的評論中的場景,但該評論已被刪除)。 –

2

我偶然發現了我現在在幾處發現的腳本。我認爲它原來從SQL Azure Team Blog在後來自2010約:

在關係數據庫中的世界循環引用其中涉及的表的外鍵創建循環模式結構。當嘗試同步執行外鍵的兩個關係數據庫時,循環引用會導致特殊類型的問題。由於此問題,包含循環引用的數據庫模式在同步和複製數據庫時可以使用的工具中受到限制。本文將解釋循環引用並演示用於確定數據庫是否具有循環引用的Transact-SQL腳本。

它也被複制here並記入韋恩貝里。也許他在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。地區 - >> dbo.State dbo.County - > dbo.Region - > dbo.Author - > dbo.City - > dbo。縣 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 。區域 dbo.Author - > dbo.City - > dbo.Author dbo.Area - > dbo.Author - > dbo.City - > dbo.County - > dbo.Region - > dbo.Image - > dbo.Area

每一行都是循環引用,並帶有創建圓的表的鏈接列表。用於檢測循環引用的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 
+0

非常感謝,這是我爲這個特殊問題找到的最好的代碼。 – valentin

0

從SQL Azure團隊博客腳本在回答1 ,它只能說明自身的引用 - 父/子關係到同一個表。 所以我寫了我自己的腳本:

-- 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 repo。請讓我知道你是否將它移植到其他RDBMS。