我们有一个主'用户'表和许多表引用UserId
现在,如果一切都很完美,删除用户应该与在主表中删除它们一样简单,并且ON CASCADE约束通过其余表将其涟漪。问题是我们不能100%确定引用的每个表中的每个FK关系(直接或间接)是否具有ON CASCADE约束。我们需要一些方法来发出删除并观察SQL Server实际触摸要删除的表。我读了this并尝试了它,但它没有显示任何级联的表 - 只是主表中的条目
以下是我的尝试:
DELETE umt
OUTPUT DELETED.*
FROM [OurAppDb].[dbo].[UserMasterTable] umt
WHERE umt.UserId LIKE 'ABCDABCD-ABCD-ABCD-ABCD-ABCDABCDABCD'
如何查看上述查询将触及的所有表格?
注意:ON CASCADE约束是我们认为在构建每个表时为每个表添加的数据库中的约束。它被添加到一个表上的示例
ALTER TABLE [dbo].[UserEmailPrefs]
WITH CHECK ADD CONSTRAINT [FK_UserEmailPrefs_UserMasterTable_UserId] FOREIGN KEY([UserId])
REFERENCES [dbo].[UserMasterTable] ([UserId])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[UserEmailPrefs] CHECK CONSTRAINT [FK_UserEmailPrefs_UserMasterTable_UserId]
GO
答案 0 :(得分:2)
要检查整个数据库中引用UserMasterTable的引用约束,请使用INFORMATION_SCHEMA视图。
SELECT RC.CONSTRAINT_NAME, TU.TABLE_NAME, RC.DELETE_RULE, RC.UPDATE_RULE
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC
INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE TU
ON RC.CONSTRAINT_CATALOG = TU.CONSTRAINT_CATALOG
AND RC.CONSTRAINT_NAME = TU.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.table_constraints TC
ON RC.unique_constraint_name = TC.CONSTRAINT_NAME
WHERE TC.TABLE_NAME='Users'
这将返回一个针对UserMasterTable的引用约束列表,并为每个引用约束,哪个表引用UserMasterTable,以及ON DELETE和ON UPDATE规则是什么。通过此,您可以快速查看哪些参照约束缺少所需的CASCADE规则。不需要触发快乐。
要将此扩展为“孙子”引用,请再添加两个join子句。 要将其扩展到任意数量的级别,请进行递归CTE。
答案 1 :(得分:1)
我认为您可以在触发器中执行此操作(分析[inserted]表并搜索从属表)。 例如:您可以创建用于存储查询以检测外键链接的表:
IF NOT EXISTS (
SELECT *
FROM [sys].tables T
WHERE T.NAME = 'FKCheck'
)
CREATE TABLE FKCheck (
TableName SYSNAME
,ChildTable SYSNAME
,QueryText NVARCHAR(MAX)
)
ELSE
EXEC('DROP TABLE FKCheck')
然后用从元数据中提取的动态查询填充
;WITH CTE_FKs
AS (
SELECT FK.NAME
,OBJECT_name(fk.parent_object_id) AS ChildTable
,OBJECT_name(fk.referenced_object_id) AS ParentTable
,delete_referential_action_desc AS DeleteAction
,MainTable.NAME AS MainTableColumn
,ChildObject.NAME AS ChildColumnName
FROM sys.foreign_keys FK
INNER JOIN sys.foreign_key_columns FKC ON FKC.constraint_object_id = FK.object_id
INNER JOIN sys.columns AS ChildObject ON ChildObject.object_id = FKc.parent_object_id
AND FKC.parent_column_id = ChildObject.column_id
INNER JOIN sys.columns AS MainTable ON MainTable.object_id = FK.referenced_object_id
AND MainTable.column_id = FKC.referenced_column_id
)
,CTE_Tables
AS (
SELECT DISTINCT C.NAME
,C.ParentTable
,C.DeleteAction
,C.ChildTable
FROM [CTE_FKs] C
)
INSERT INTO [dbo].[FKCheck] (
TableName
,ChildTable
,QueryText
)
SELECT C.ParentTable,C.ChildTable
,'IF EXISTS (select 1 from inserted INNER JOIN ' + QUOTENAME(C.ChildTable) + ' ON ' + STUFF((
SELECT ' AND inserted.' + QUOTENAME(C2.MainTableColumn) + ' = ' + + QUOTENAME(C2.ChildTable) + '.' + QUOTENAME(C2.ChildColumnName)
FROM CTE_FKs C2
WHERE C2.ParentTable = C.ParentTable
AND C2.NAME = C.NAME
FOR XML PATH('')
,TYPE
).value('.', 'nvarchar(MAX)'), 1, 4, '') + ')
RAISERROR(''Relation with ' + QUOTENAME(C.ChildTable) +':'+ CASE C.DeleteAction
WHEN 'CASCADE'
THEN ' data will be deleted'
WHEN 'SET_NULL'
THEN ' set as NULL'
WHEN 'NO_ACTION'
THEN ' no default action'
ELSE 'Unknown'
END + ''')'
FROM [CTE_Tables] C
您在表格中的查询将如下所示:
IF EXISTS (select 1 from inserted INNER JOIN [UserEmailPrefs] ON inserted.[UserId] = [UserEmailPrefs].[UserId])
RAISERROR('Relation with [UserEmailPrefs]: no default action')
IF EXISTS (select 1 from inserted INNER JOIN [UserEmail] ON inserted.[UserId] = [UserEmail].[UserId])
RAISERROR('Relation with [UserEmail]: set as NULL')
然后在触发器中,您可以执行查询以打印消息:
DECLARE @TableName SYSNAME = 'UserMasterTable';
DECLARE @sSQL NVARCHAR(MAX) = '';
SELECT @sSQL += F.QueryText + CHAR(10)
FROM FKCheck F
WHERE F.TableName = @TableName;
EXEC(@sSQL)
ROLLBACK
如果您需要分析更多“远程”表,则需要在FKCheck表中遍历层次结构。