2008-12-09 4 views
17

Le SQL suivant sépare les tables en fonction de leur relation. Le problème est avec les tables qui trient sous la série 3000. Tables faisant partie de clés étrangères et utilisant des clés étrangères. Quelqu'un a eu un CTE récursif intelligent de préférence ou une procédure stockée pour faire le tri nécessaire ?? Les programmes connectiong à la base de données ne sont pas considérés comme une solution.SQLServer: Comment trier les noms de tables classés par leur dépendance de clé étrangère

Editer: J'ai posté la réponse dans les "réponses" sur la base de la première solution Libre "bonne réponse" à avoir pour quiconque posant ma propre "bonne" réponse!

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

Salut, Comment trier les tables avec "3000" comme vous l'avez mentionné - - Ceux-ci doivent être triés en fonction de leurs relations internes. Requête dans la réponse ci-dessous accepté retournant 0 lignes à moi. –

Répondre

8

Merci pour une solution de travail NXC. Vous me mettez sur la bonne voie pour résoudre le problème en utilisant un CTE récursif.

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 

Pour thoose se demander ce que cela est utilisable pour: je vais l'utiliser en toute sécurité vider une base de données sans violer les relations clés étrangères. (En tronquant dans l'ordre décroissant) Je serai également en mesure de remplir en toute sécurité les tables avec les données d'une autre base de données en remplissant les tableaux dans l'ordre croissant.

+0

sympa! J'ai eu un problème similaire et ce script m'a totalement sauvé! la seule chose que je devais changer est d'utiliser 'is_identity' au lieu de 'is_rowguidcol' – ironic

2

Vous pouvez utiliser un algorithme itératif, probablement moins compliqué qu'un CTE. Voici un exemple qui trie selon la profondeur:

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

Cela provoque des problèmes avec les tables auto-référencées. Vous devrez exclure manuellement les clés étrangères qui pointent vers des tables auto-référencées.

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

Mon interprétation avec quelques réglages modérés: Celui-ci est SQL-2005 + et fonctionne sur les bases de données sans "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 
Questions connexes