2008-12-09 16 views
17

El siguiente SQL separa las tablas según su relación. El problema es con las tablas que se clasifican en la serie 3000. Tablas que son parte de claves externas y que usan claves externas. ¿Alguien tiene algún CTE recursivo inteligente preferiblemente o un procedimiento almacenado para hacer la clasificación necesaria? Los programas que se conectan a la base de datos no se consideran una solución.SQLServer: cómo ordenar los nombres de tabla ordenados por su dependencia de clave externa

Editar: He publicado la respuesta en las "respuestas" basadas en la primera solución ¡Hay una "respuesta correcta" para cualquier persona que haya publicado mi propia respuesta "correcta"!

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

Hola, ¿Cómo ordenar las tablas con "3000" como usted ha mencionado - - Estos deben ordenarse en función de sus relaciones internas. Consulta en la respuesta aceptada a continuación que me devuelve 0 filas. –

Respuesta

8

Gracias por una solución de trabajo NXC. Me pones en el camino correcto para resolver el problema usando un CTE recursivo.

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 

Para thoose preguntando qué esto es utilizable para: lo voy a utilizar para vaciar de forma segura una base de datos sin violar ninguna relación de clave externa. (Al truncar en orden descendente) También podré llenar las tablas de forma segura con datos de otra base de datos llenando las tablas en orden ascendente.

+0

¡agradable! ¡Tuve un problema similar y ese script me salvó totalmente! Lo único que necesitaba cambiar es usar 'is_identity' en lugar de 'is_rowguidcol' – ironic

2

Puede utilizar un algoritmo iterativo, que es probablemente menos intrincado que un CTE. Aquí hay un ejemplo que ordena según la profundidad:

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

Esto causa problemas con las tablas de autorreferencia. Tendrá que excluir manualmente cualquier clave externa que apunte a tablas de autorreferencia.

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

Mi interpretación con ajustes moderados: Éste es SQL-2005 + y trabaja en las bases de datos sin la "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 
Cuestiones relacionadas