2008-12-09 87 views
17

以下SQL根據表之間的關係分隔表。問題在於3000系列下的表格。作爲外鍵的一部分並使用外鍵的表。任何人最好有一些聰明的遞歸CTE或存儲過程來做必要的排序?連接到數據庫的程序不被視爲解決方案。SQLServer:如何對按其外鍵依賴項排序的表名進行排序

編輯:我根據第一個解決方案在「答案」中發佈了答案 對於任何人重新發布我自己的「正確」答案的免費「正確答案」!

WITH 
AllTables(TableName) AS 
(
SELECT OBJECT_SCHEMA_NAME(so.id) +'.'+ OBJECT_NAME(so.id) 
FROM dbo.sysobjects so 
INNER JOIN sys.all_columns ac ON 
    so.ID = ac.object_id 
WHERE 
    so.type = 'U' 
AND 
    ac.is_rowguidcol = 1 
), 

    Relationships(ReferenceTableName, ReferenceColumnName, TableName, ColumnName) AS 
    (
    SELECT 
    OBJECT_SCHEMA_NAME (fkey.referenced_object_id) + '.' + 
    OBJECT_NAME (fkey.referenced_object_id) AS ReferenceTableName 
    ,COL_NAME(fcol.referenced_object_id, 
       fcol.referenced_column_id) AS ReferenceColumnName 
    ,OBJECT_SCHEMA_NAME (fkey.parent_object_id) + '.' + 
    OBJECT_NAME(fkey.parent_object_id) AS TableName 
    ,COL_NAME(fcol.parent_object_id, fcol.parent_column_id) AS ColumnName 
    FROM sys.foreign_keys AS fkey 
    INNER JOIN sys.foreign_key_columns AS fcol ON 
       fkey.OBJECT_ID = fcol.constraint_object_id 
), 

NotReferencedOrReferencing(TableName) AS 
(
SELECT TableName FROM AllTables 
EXCEPT 
SELECT TableName FROM Relationships 
EXCEPT 
SELECT ReferenceTableName FROM Relationships 
), 

OnlyReferenced(Tablename) AS 
(
SELECT ReferenceTableName FROM Relationships 
EXCEPT 
SELECT TableName FROM Relationships 
), 
-- These need to be sorted based on theire internal relationships 
ReferencedAndReferencing(TableName, ReferenceTableName) AS 
(
SELECT r1.Tablename, r2.ReferenceTableName FROM Relationships r1 
INNER JOIN Relationships r2 
ON r1.TableName = r2.ReferenceTableName 
), 

OnlyReferencing(TableName) AS 
(
SELECT Tablename FROM Relationships 
EXCEPT 
SELECT ReferenceTablename FROM Relationships 
) 
SELECT TableName, 1000 AS Sorting FROM NotReferencedOrReferencing 
UNION 
SELECT TableName, 2000 AS Sorting FROM OnlyReferenced 
UNION 
SELECT TableName, 3000 AS Sorting FROM ReferencedAndReferencing 
UNION 
SELECT TableName, 4000 AS Sorting FROM OnlyReferencing 
ORDER BY Sorting 
+0

嗨,你提到你如何排序與「3000」表 - - 這些需要根據國內關係進行排序。在下面的查詢答案中返回0行給我。 – 2012-07-05 10:12:55

回答

8

感謝您的工作溶液NXC。你把我放在正確的軌道上,用遞歸CTE解決問題。

WITH 
    TablesCTE(TableName, TableID, Ordinal) AS 
    (
    SELECT 
    OBJECT_SCHEMA_NAME(so.id) +'.'+ OBJECT_NAME(so.id) AS TableName, 
    so.id AS TableID, 
    0 AS Ordinal 
    FROM dbo.sysobjects so INNER JOIN sys.all_columns ac ON so.ID = ac.object_id 
    WHERE 
    so.type = 'U' 
    AND 
    ac.is_rowguidcol = 1 
    UNION ALL 
    SELECT 
    OBJECT_SCHEMA_NAME(so.id) +'.'+ OBJECT_NAME(so.id) AS TableName, 
    so.id AS TableID, 
    tt.Ordinal + 1 AS Ordinal 
    FROM 
    dbo.sysobjects so 
    INNER JOIN sys.all_columns ac ON so.ID = ac.object_id 
    INNER JOIN sys.foreign_keys f 
     ON (f.parent_object_id = so.id AND f.parent_object_id != f.referenced_object_id) 
    INNER JOIN TablesCTE tt ON f.referenced_object_id = tt.TableID 
    WHERE 
    so.type = 'U' 
    AND 
    ac.is_rowguidcol = 1 
) 
SELECT DISTINCT 
    t.Ordinal, 
    t.TableName 
    FROM TablesCTE t 
    INNER JOIN 
    (
    SELECT 
     TableName as TableName, 
     Max (Ordinal) as Ordinal 
    FROM TablesCTE 
    GROUP BY TableName 
    ) tt ON (t.TableName = tt.TableName AND t.Ordinal = tt.Ordinal) 
ORDER BY t.Ordinal, t.TableName 

對於想知道這可用於什麼:我將使用它來安全地清空數據庫而不會違反任何外鍵關係。 (按降序截斷) 我還可以通過按升序填寫表格來安全地填充來自另一個數據庫的數據。

+0

不錯!我有類似的問題,劇本完全救了我!唯一需要改變的是使用'is_identity'而不是'is_rowguidcol' – ironic 2013-06-20 08:42:41

2

您可以使用迭代算法,這可能比CTE的卷積更小。下面是根據深度排序的例子:

declare @level int  -- Current depth 
     ,@count int  

-- Step 1: Start with tables that have no FK dependencies 
-- 
if object_id ('tempdb..#Tables') is not null 
    drop table #Tables 

select s.name + '.' + t.name as TableName 
     ,t.object_id   as TableID 
     ,0      as Ordinal 
    into #Tables 
    from sys.tables t 
    join sys.schemas s 
    on t.schema_id = s.schema_id 
where not exists 
     (select 1 
      from sys.foreign_keys f 
     where f.parent_object_id = t.object_id) 

set @count = @@rowcount   
set @level = 0 


-- Step 2: For a given depth this finds tables joined to 
-- tables at this given depth. A table can live at multiple 
-- depths if it has more than one join path into it, so we 
-- filter these out in step 3 at the end. 
-- 
while @count > 0 begin 

    insert #Tables (
      TableName 
      ,TableID 
      ,Ordinal 
    ) 
    select s.name + '.' + t.name as TableName 
      ,t.object_id   as TableID 
      ,@level + 1    as Ordinal 
     from sys.tables t 
     join sys.schemas s 
     on s.schema_id = t.schema_id 
    where exists 
      (select 1 
       from sys.foreign_keys f 
       join #Tables tt 
       on f.referenced_object_id = tt.TableID 
       and tt.Ordinal = @level 
       and f.parent_object_id = t.object_id 
       and f.parent_object_id != f.referenced_object_id) 
        -- The last line ignores self-joins. You'll 
        -- need to deal with these separately 

    set @count = @@rowcount 
    set @level = @level + 1 
end 

-- Step 3: This filters out the maximum depth an object occurs at 
-- and displays the deepest first. 
-- 
select t.Ordinal 
     ,t.TableID 
     ,t.TableName 
    from #Tables t 
    join (select TableName  as TableName 
       ,Max (Ordinal) as Ordinal 
      from #Tables 
     group by TableName) tt 
    on t.TableName = tt.TableName 
    and t.Ordinal = tt.Ordinal 
order by t.Ordinal desc 
0

這會導致自引用表出現問題。您需要手動排除指向自引用表的所有外鍵。

INNER JOIN sys.foreign_keys f 
    ON (f.parent_object_id = so.id AND f.parent_object_id != f.referenced_object_id) 

    /* Manually exclude self-referencing tables - they cause recursion problems*/  
    and f.object_id not in /*Below are IDs of foreign keys*/ 
     (1847729685, 
     1863729742,  
     1879729799  
     ) 
INNER JOIN TablesCTE tt 
12

我的演繹與適度的調整:這一個是SQL-2005 +和數據庫作品,未經「ROWGUIDCOL」:

WITH TablesCTE(SchemaName, TableName, TableID, Ordinal) AS 
(
    SELECT 
     OBJECT_SCHEMA_NAME(so.object_id) AS SchemaName, 
     OBJECT_NAME(so.object_id) AS TableName, 
     so.object_id AS TableID, 
     0 AS Ordinal 
    FROM 
     sys.objects AS so 
    WHERE 
     so.type = 'U' 
     AND so.is_ms_Shipped = 0 
    UNION ALL 
    SELECT 
     OBJECT_SCHEMA_NAME(so.object_id) AS SchemaName, 
     OBJECT_NAME(so.object_id) AS TableName, 
     so.object_id AS TableID, 
     tt.Ordinal + 1 AS Ordinal 
    FROM 
     sys.objects AS so 
    INNER JOIN sys.foreign_keys AS f 
     ON f.parent_object_id = so.object_id 
     AND f.parent_object_id != f.referenced_object_id 
    INNER JOIN TablesCTE AS tt 
     ON f.referenced_object_id = tt.TableID 
    WHERE 
     so.type = 'U' 
     AND so.is_ms_Shipped = 0 
) 

SELECT DISTINCT 
     t.Ordinal, 
     t.SchemaName, 
     t.TableName, 
     t.TableID 
    FROM 
     TablesCTE AS t 
    INNER JOIN 
     (
      SELECT 
       itt.SchemaName as SchemaName, 
       itt.TableName as TableName, 
       itt.TableID as TableID, 
       Max(itt.Ordinal) as Ordinal 
      FROM 
       TablesCTE AS itt 
      GROUP BY 
       itt.SchemaName, 
       itt.TableName, 
       itt.TableID 
     ) AS tt 
     ON t.TableID = tt.TableID 
     AND t.Ordinal = tt.Ordinal 
ORDER BY 
    t.Ordinal, 
    t.TableName