Подтвердить что ты не робот

SQL Script, чтобы изменить ВСЕ внешние ключи, чтобы добавить ON DELETE CASCADE

У меня есть база данных SQL 2005 с примерно 250 таблицами.

Я хочу временно включить ON DELETE CASCADE для всех внешних ключей, чтобы я мог легко выполнять массовое удаление.

Затем я хочу отключить ON DELETE CASCADE на всех внешних ключах.

Единственный способ, которым я знаю это сделать, - использовать Management Studio для создания полной базы данных create script, выполнить какой-то поиск и замену, чтобы отключить все, кроме внешних ключей, сохранить script, а затем сделать еще несколько поиска и замены для добавления ON DELETE CASCADE.

Затем я запустил script, выполнил мое удаление, а затем запустил другой script.

Есть ли более простой способ создать этот script? Этот метод кажется слишком подверженным ошибкам, и мне нужно будет обновить script с любыми другими изменениями, которые мы делаем в базе данных, или повторно генерировать его вручную каждый раз, когда мне может понадобиться его использовать.

Является альтернативным вариантом для запуска выбора в системных таблицах для "генерации" script для меня? Возможно ли даже запустить обновление в системной таблице, которая включает и отключает ON DELETE CASCADE?

4b9b3361

Ответ 1

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

select
  DropStmt = 'ALTER TABLE [' + ForeignKeys.ForeignTableSchema + 
      '].[' + ForeignKeys.ForeignTableName + 
      '] DROP CONSTRAINT [' + ForeignKeys.ForeignKeyName + ']; '
,  CreateStmt = 'ALTER TABLE [' + ForeignKeys.ForeignTableSchema + 
      '].[' + ForeignKeys.ForeignTableName + 
      '] WITH CHECK ADD CONSTRAINT [' +  ForeignKeys.ForeignKeyName + 
      '] FOREIGN KEY([' + ForeignKeys.ForeignTableColumn + 
      ']) REFERENCES [' + schema_name(sys.objects.schema_id) + '].[' +
  sys.objects.[name] + ']([' +
  sys.columns.[name] + ']) ON DELETE CASCADE; '
 from sys.objects
  inner join sys.columns
    on (sys.columns.[object_id] = sys.objects.[object_id])
  inner join (
    select sys.foreign_keys.[name] as ForeignKeyName
     ,schema_name(sys.objects.schema_id) as ForeignTableSchema
     ,sys.objects.[name] as ForeignTableName
     ,sys.columns.[name]  as ForeignTableColumn
     ,sys.foreign_keys.referenced_object_id as referenced_object_id
     ,sys.foreign_key_columns.referenced_column_id as referenced_column_id
     from sys.foreign_keys
      inner join sys.foreign_key_columns
        on (sys.foreign_key_columns.constraint_object_id
          = sys.foreign_keys.[object_id])
      inner join sys.objects
        on (sys.objects.[object_id]
          = sys.foreign_keys.parent_object_id)
        inner join sys.columns
          on (sys.columns.[object_id]
            = sys.objects.[object_id])
           and (sys.columns.column_id
            = sys.foreign_key_columns.parent_column_id)
    ) ForeignKeys
    on (ForeignKeys.referenced_object_id = sys.objects.[object_id])
     and (ForeignKeys.referenced_column_id = sys.columns.column_id)
 where (sys.objects.[type] = 'U')
  and (sys.objects.[name] not in ('sysdiagrams'))

Ответ 2

Ответ Andomar выше хорош, но работает только для ограничений внешнего ключа в один столбец. Я немного адаптировал его для ограничений нескольких столбцов:

create function dbo.fk_columns (@constraint_object_id int)
returns varchar(255)
as begin
declare @r varchar(255)
select @r = coalesce(@r + ',', '') + c.name
from sys.foreign_key_columns fkc
join sys.columns c
  on  fkc.parent_object_id = c.object_id
  and fkc.parent_column_id = c.column_id
where fkc.constraint_object_id = @constraint_object_id
return @r
end

select distinct
  DropStmt = 'ALTER TABLE [' + ForeignKeys.ForeignTableSchema +
      '].[' + ForeignKeys.ForeignTableName +
      '] DROP CONSTRAINT [' + ForeignKeys.ForeignKeyName + '] '
,  CreateStmt = 'ALTER TABLE [' + ForeignKeys.ForeignTableSchema +
  '].[' + ForeignKeys.ForeignTableName +
  '] WITH CHECK ADD CONSTRAINT [' +  ForeignKeys.ForeignKeyName +
  '] FOREIGN KEY(' + dbo.fk_columns(constraint_object_id) + ')' +
  'REFERENCES [' + schema_name(sys.objects.schema_id) + '].[' +
  sys.objects.[name] + '] '
  + ' ON DELETE CASCADE'
 from sys.objects
  inner join sys.columns
    on (sys.columns.[object_id] = sys.objects.[object_id])
  inner join (
select sys.foreign_keys.[name] as ForeignKeyName
 ,schema_name(sys.objects.schema_id) as ForeignTableSchema
 ,sys.objects.[name] as ForeignTableName
 ,sys.columns.[name]  as ForeignTableColumn
 ,sys.foreign_keys.referenced_object_id as referenced_object_id
 ,sys.foreign_key_columns.referenced_column_id as referenced_column_id
 ,sys.foreign_keys.object_id as constraint_object_id
 from sys.foreign_keys
  inner join sys.foreign_key_columns
    on (sys.foreign_key_columns.constraint_object_id
      = sys.foreign_keys.[object_id])
  inner join sys.objects
    on (sys.objects.[object_id]
      = sys.foreign_keys.parent_object_id)
    inner join sys.columns
      on (sys.columns.[object_id]
        = sys.objects.[object_id])
       and (sys.columns.column_id
        = sys.foreign_key_columns.parent_column_id)
-- Uncomment this if you want to include only FKs that already
-- have a cascade constraint.
--       where (delete_referential_action_desc = 'CASCADE' or update_referential_action_desc = 'CASCADE')
) ForeignKeys
on (ForeignKeys.referenced_object_id = sys.objects.[object_id])
 and (ForeignKeys.referenced_column_id = sys.columns.column_id)
 where (sys.objects.[type] = 'U')
  and (sys.objects.[name] not in ('sysdiagrams'))

Вы также можете использовать этот запрос, чтобы помочь удалить ON DELETE CASCADE из тех FK, у которых он есть.

Это все еще не обрабатывает случай, когда столбцы именуются в разных таблицах по-разному - для этого должна быть определена другая пользовательская функция.

Ответ 3

Решение, соответствующее стандартам.

;WITH CTE AS 
(
    SELECT  
         KCU1.CONSTRAINT_NAME AS FK_CONSTRAINT_NAME 
        ,KCU1.TABLE_SCHEMA AS FK_SCHEMA_NAME 
        ,KCU1.TABLE_NAME AS FK_TABLE_NAME 
        ,KCU1.COLUMN_NAME AS FK_COLUMN_NAME 
        ,KCU1.ORDINAL_POSITION AS FK_ORDINAL_POSITION 
        ,KCU2.CONSTRAINT_NAME AS REFERENCED_CONSTRAINT_NAME 
        ,KCU2.TABLE_SCHEMA AS REFERENCED_SCHEMA_NAME 
        ,KCU2.TABLE_NAME AS REFERENCED_TABLE_NAME 
        ,KCU2.COLUMN_NAME AS REFERENCED_COLUMN_NAME 
        ,KCU2.ORDINAL_POSITION AS REFERENCED_ORDINAL_POSITION 
    FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC 

    INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1 
        ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG  
        AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA 
        AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME 

    INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2 
        ON KCU2.CONSTRAINT_CATALOG = RC.UNIQUE_CONSTRAINT_CATALOG  
        AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA 
        AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME 
        AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION 
)


SELECT 
     FK_CONSTRAINT_NAME
    --,FK_SCHEMA_NAME
    --,FK_TABLE_NAME
    --,FK_COLUMN_NAME
    --,FK_ORDINAL_POSITION
    --,REFERENCED_CONSTRAINT_NAME
    --,REFERENCED_SCHEMA_NAME
    --,REFERENCED_TABLE_NAME
    --,REFERENCED_COLUMN_NAME
    --,REFERENCED_ORDINAL_POSITION

    ,
        'ALTER TABLE [' + FK_SCHEMA_NAME + ']' 
        + '.[' + FK_TABLE_NAME + '] ' 
        + 'DROP CONSTRAINT [' + FK_CONSTRAINT_NAME + ']; ' 
    AS DropStmt 

    ,
    'ALTER TABLE [' + FK_SCHEMA_NAME + ']' 
        + '.[' + FK_TABLE_NAME + '] ' + 
        + 'WITH CHECK ADD CONSTRAINT [' +  FK_CONSTRAINT_NAME + '] ' 
        + 'FOREIGN KEY([' + FK_COLUMN_NAME + ']) ' 
        + 'REFERENCES [' + REFERENCED_SCHEMA_NAME + '].[' + REFERENCED_TABLE_NAME + ']([' + REFERENCED_COLUMN_NAME + ']) ON DELETE CASCADE; ' 
    AS CreateStmt 

FROM CTE 

WHERE (1=1) 
/*
AND FK_TABLE_NAME IN 
(
     'T_SYS_Geschossrechte'
    ,'T_SYS_Gebaeuderechte'
    ,'T_SYS_Standortrechte'
)

AND REFERENCED_TABLE_NAME NOT LIKE 'T_AP_Ref_Mandant'
*/

ORDER BY  
     FK_TABLE_NAME
    ,FK_CONSTRAINT_NAME
    ,FK_COLUMN_NAME
    ,FK_ORDINAL_POSITION
    ,REFERENCED_CONSTRAINT_NAME
    ,REFERENCED_TABLE_NAME
    ,REFERENCED_COLUMN_NAME
    ,REFERENCED_ORDINAL_POSITION

Edit:
Расширен для внешних столбцов с несколькими столбцами:

;WITH CTE AS 
( 
  SELECT  
         KCU1.CONSTRAINT_NAME AS FK_CONSTRAINT_NAME 
        ,KCU1.TABLE_SCHEMA AS FK_SCHEMA_NAME 
        ,KCU1.TABLE_NAME AS FK_TABLE_NAME 
        ,KCU1.COLUMN_NAME AS FK_COLUMN_NAME 
        ,KCU1.ORDINAL_POSITION AS FK_ORDINAL_POSITION 
        ,KCU2.CONSTRAINT_NAME AS REFERENCED_CONSTRAINT_NAME 
        ,KCU2.TABLE_SCHEMA AS REFERENCED_SCHEMA_NAME 
        ,KCU2.TABLE_NAME AS REFERENCED_TABLE_NAME 
        ,KCU2.COLUMN_NAME AS REFERENCED_COLUMN_NAME 
        ,KCU2.ORDINAL_POSITION AS REFERENCED_ORDINAL_POSITION 
    FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC 

    INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1 
        ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG  
        AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA 
        AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME 

    INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2 
        ON KCU2.CONSTRAINT_CATALOG = RC.UNIQUE_CONSTRAINT_CATALOG  
        AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA 
        AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME 
        AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION 
)
SELECT 
     FK_SCHEMA_NAME
    ,FK_TABLE_NAME
    ,FK_CONSTRAINT_NAME 
    --,FK_COLUMN_NAME
    --,REFERENCED_COLUMN_NAME



    ,
    'ALTER TABLE ' + QUOTENAME(FK_SCHEMA_NAME) + '.' + QUOTENAME(FK_TABLE_NAME) + ' ' 
      + 'DROP CONSTRAINT ' + QUOTENAME(FK_CONSTRAINT_NAME) + '; ' 
    AS DropStmt 

    ,
    'ALTER TABLE ' + QUOTENAME(FK_SCHEMA_NAME) + '.' + QUOTENAME(FK_TABLE_NAME) + ' 
    ADD CONSTRAINT ' + QUOTENAME(FK_CONSTRAINT_NAME) + ' 
    FOREIGN KEY('
    + 
        SUBSTRING 
        ( 
            (
                SELECT ', ' + QUOTENAME(FK.FK_COLUMN_NAME) AS [text()] 
                FROM CTE AS FK 
                WHERE FK.FK_CONSTRAINT_NAME = CTE.FK_CONSTRAINT_NAME
                AND FK.FK_SCHEMA_NAME = CTE.FK_SCHEMA_NAME
                AND FK.FK_TABLE_NAME = CTE.FK_TABLE_NAME 
                ORDER BY FK.FK_ORDINAL_POSITION
                FOR XML PATH, TYPE 
            ).value('.[1]', 'nvarchar(MAX)')
            ,3, 4000
        ) 
    + ') 
'
    + '    REFERENCES ' + QUOTENAME(REFERENCED_SCHEMA_NAME) + '.' + QUOTENAME(REFERENCED_TABLE_NAME) + '(' 
    + SUBSTRING 
      ( 
          (
              SELECT ', ' + QUOTENAME(Referenced.REFERENCED_COLUMN_NAME) AS [text()] 
              FROM CTE AS Referenced 
              WHERE Referenced.FK_CONSTRAINT_NAME = CTE.FK_CONSTRAINT_NAME
              AND Referenced.REFERENCED_SCHEMA_NAME = CTE.REFERENCED_SCHEMA_NAME
              AND Referenced.REFERENCED_TABLE_NAME = CTE.REFERENCED_TABLE_NAME
              ORDER BY Referenced.REFERENCED_ORDINAL_POSITION 
              FOR XML PATH, TYPE 
          ).value('.[1]', 'nvarchar(MAX)')
          , 3, 4000 
      )
    + ') 
    ON DELETE CASCADE 
; ' AS CreateStmt 

FROM CTE 

GROUP BY 
     FK_SCHEMA_NAME
    ,FK_TABLE_NAME
    ,FK_CONSTRAINT_NAME 

    ,REFERENCED_SCHEMA_NAME
    ,REFERENCED_TABLE_NAME

И гораздо более простая версия для PostGreSQL:

;WITH CTE AS 
( 
  SELECT  
         KCU1.CONSTRAINT_NAME AS FK_CONSTRAINT_NAME 
        ,KCU1.TABLE_SCHEMA AS FK_SCHEMA_NAME 
        ,KCU1.TABLE_NAME AS FK_TABLE_NAME 
        ,KCU1.COLUMN_NAME AS FK_COLUMN_NAME 
        ,KCU1.ORDINAL_POSITION AS FK_ORDINAL_POSITION 
        ,KCU2.CONSTRAINT_NAME AS REFERENCED_CONSTRAINT_NAME 
        ,KCU2.TABLE_SCHEMA AS REFERENCED_SCHEMA_NAME 
        ,KCU2.TABLE_NAME AS REFERENCED_TABLE_NAME 
        ,KCU2.COLUMN_NAME AS REFERENCED_COLUMN_NAME 
        ,KCU2.ORDINAL_POSITION AS REFERENCED_ORDINAL_POSITION 
    FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC 

    INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1 
        ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG  
        AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA 
        AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME 

    INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2 
        ON KCU2.CONSTRAINT_CATALOG = RC.UNIQUE_CONSTRAINT_CATALOG  
        AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA 
        AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME 
        AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION 
)
SELECT 
     FK_SCHEMA_NAME
    ,FK_TABLE_NAME
    ,FK_CONSTRAINT_NAME 
    --,FK_COLUMN_NAME
    --,REFERENCED_COLUMN_NAME



    ,
    'ALTER TABLE ' || QUOTE_IDENT(FK_SCHEMA_NAME) || '.' || QUOTE_IDENT(FK_TABLE_NAME) || ' ' 
      || 'DROP CONSTRAINT ' || QUOTE_IDENT(FK_CONSTRAINT_NAME) || '; ' 
    AS DropStmt 

    ,
    'ALTER TABLE ' || QUOTE_IDENT(FK_SCHEMA_NAME) || '.' || QUOTE_IDENT(FK_TABLE_NAME) || ' 
    ADD CONSTRAINT ' || QUOTE_IDENT(FK_CONSTRAINT_NAME) || ' 
    FOREIGN KEY(' || string_agg(FK_COLUMN_NAME, ', ') || ') 
'
    || '    REFERENCES ' || QUOTE_IDENT(REFERENCED_SCHEMA_NAME) || '.' || QUOTE_IDENT(REFERENCED_TABLE_NAME) || '(' || string_agg(REFERENCED_COLUMN_NAME, ', ') || ') 
    ON DELETE CASCADE 
; ' AS CreateStmt 

FROM CTE 

GROUP BY 
     FK_SCHEMA_NAME
    ,FK_TABLE_NAME
    ,FK_CONSTRAINT_NAME 

    ,REFERENCED_SCHEMA_NAME
    ,REFERENCED_TABLE_NAME

Ответ 4

Вам придется изменить таблицу, отбросить ограничения FK и заново создать их:

Это синтаксис db2. SQLServer должен быть похож на

ALTER TABLE emp DROP CONSTRAINT fk_dept;

ALTER TABLE emp ADD CONSTRAINT fk_dept
FOREIGN KEY(dept_no)
REFERENCES dept(deptno)
ON DELETE CASCADE;

Вы можете написать собственный sp, чтобы запросить системную таблицу для всех внешних ключей, отбросить их и заново создать. Вам нужно будет использовать динамический sql в sp, чтобы сделать это, где вы можете пропустить fk defn, поместить их в varchar и добавить/редактировать, чтобы включить CASCADE, а затем выполнить stmt.

Ответ 5

Ответ от @Andomar работал для меня, но он был немного ручным - вы должны запустить его, затем скопировать результаты и запустить их. Мне нужно было использовать это как часть моей автоматической настройки теста, поэтому он должен был запускаться автоматически в одном запросе.

Я пришел к следующему: он запускает весь SQL для изменения ограничений внешнего ключа, а затем фактически запускает все это за один раз:

    IF Object_id('tempdb..#queriesForContraints') IS NOT NULL 
      BEGIN 
          DROP TABLE #queriesForContraints
      END 

    DECLARE @ignoreTablesCommaSeparated VARCHAR(1000)

    SELECT 'ALTER TABLE [' 
           + ForeignKeys.foreigntableschema + '].[' 
           + ForeignKeys.foreigntablename 
           + '] DROP CONSTRAINT [' 
           + ForeignKeys.foreignkeyname + ']; ' 
           + 'ALTER TABLE [' 
           + ForeignKeys.foreigntableschema + '].[' 
           + ForeignKeys.foreigntablename 
           + '] WITH CHECK ADD CONSTRAINT [' 
           + ForeignKeys.foreignkeyname 
           + '] FOREIGN KEY([' 
           + ForeignKeys.foreigntablecolumn 
           + ']) REFERENCES [' 
           + Schema_name(sys.objects.schema_id) + '].[' 
           + sys.objects.[name] + ']([' 
           + sys.columns.[name] 
           + ']) ON DELETE CASCADE; ' AS query
    INTO #queriesForContraints
    FROM   sys.objects 
           INNER JOIN sys.columns 
                   ON ( sys.columns.[object_id] = sys.objects.[object_id] ) 
           INNER JOIN (SELECT sys.foreign_keys.[name]                      AS 
                              ForeignKeyName, 
                              Schema_name(sys.objects.schema_id)           AS 
                  ForeignTableSchema, 
                              sys.objects.[name]                           AS 
                  ForeignTableName, 
                              sys.columns.[name]                           AS 
                  ForeignTableColumn, 
                              sys.foreign_keys.referenced_object_id        AS 
                  referenced_object_id, 
                              sys.foreign_key_columns.referenced_column_id AS 
                  referenced_column_id 
                       FROM   sys.foreign_keys 
                              INNER JOIN sys.foreign_key_columns 
                                      ON ( 
                              sys.foreign_key_columns.constraint_object_id = 
                              sys.foreign_keys.[object_id] ) 
                              INNER JOIN sys.objects 
                                      ON ( sys.objects.[object_id] = 
                                         sys.foreign_keys.parent_object_id ) 
                              INNER JOIN sys.columns 
                                      ON ( sys.columns.[object_id] = 
                                           sys.objects.[object_id] ) 
                                         AND ( sys.columns.column_id = 
    sys.foreign_key_columns.parent_column_id )) 
    ForeignKeys 
    ON ( ForeignKeys.referenced_object_id = sys.objects.[object_id] ) 
    AND ( ForeignKeys.referenced_column_id = sys.columns.column_id ) 
    WHERE  ( sys.objects.[type] = 'U' ) 
           AND ( sys.objects.[name] NOT IN ( 
                'sysdiagrams' --add more comma separated table names here if required
            ) ) 

    DECLARE @queryToRun NVARCHAR(MAX)

    SELECT  @queryToRun = STUFF(
            (SELECT query + ''
             FROM #queriesForContraints
             FOR XML PATH (''))
         , 1, 0, '')

    EXEC sp_executesql @statement = @queryToRun

    IF Object_id('tempdb..#queriesForContraints') IS NOT NULL 
      BEGIN 
          DROP TABLE #queriesForContraints
      END 

Ответ 6

создать новую хранимую процедуру, где единственным параметром является имя обрабатываемой таблицы. В этой процедуре вам нужно будет перебрать sys.foreign_keys и sys.foreign_key_columns, чтобы создать правильный синтаксис drop и create, просто используйте курсор и некоторые отпечатки (KISS).

вызовите эту процедуру с помощью синтаксиса:

EXEC sp_msforeachtable 'YourProcedureName ''?'''

и он будет запускаться для каждой таблицы. Возьмите и запустите вывод, и все готово.

Ответ 7

Простое решение, которое я нашел, состоит в том, чтобы экспортировать базу данных в один файл, использовать функцию поиска, чтобы заменить все NO ACTION на CASCADE, удалить базу данных и импортировать отредактированный файл.

Информация, измененная в базе данных между экспортом и импортом, будет потеряна.