SQLServer: как сортировать имена таблиц в порядке их зависимости от внешнего ключа - PullRequest
19 голосов
/ 09 декабря 2008

Следующий SQL разделяет таблицы в соответствии с их отношениями. Проблема с таблицами, которые сортируются под серией 3000. Таблицы, являющиеся частью внешних ключей и использующие внешние ключи. Кто-нибудь получил какой-нибудь умный рекурсивный CTE предпочтительно или хранимую процедуру для выполнения необходимой сортировки Программы подключения к базе данных не считаются решением.

Изменить: я разместил ответ в «ответы» на основе первого решения Бесплатный «правильный ответ» для каждого, кто будет размещать мой собственный «правильный» ответ!

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

Ответы [ 4 ]

14 голосов
/ 08 декабря 2010

Мое представление с умеренными изменениями: это 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
9 голосов
/ 11 декабря 2008

Спасибо за рабочий раствор 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

Для тех, кто интересуется, для чего это нужно: я буду использовать его для безопасного опустошения базы данных без нарушения каких-либо отношений с внешним ключом. (Обрезая в порядке убывания) Я также смогу безопасно заполнять таблицы данными из другой базы данных, заполняя таблицы в порядке возрастания.

2 голосов
/ 09 декабря 2008

Вы можете использовать итерационный алгоритм, который, вероятно, менее запутан, чем 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 голосов
/ 22 января 2010

Это вызывает проблемы с самообращающимися таблицами. Вам нужно будет вручную исключить любые внешние ключи, которые указывают на самоссылающиеся таблицы.

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...