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?
Ответы
Ответ 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, удалить базу данных и импортировать отредактированный файл.
Информация, измененная в базе данных между экспортом и импортом, будет потеряна.