关于sql server:暂时禁用所有外键约束

Temporarily disable all foreign key constraints

我正在运行一个ssis包,它将替换从平面文件到数据库中现有表的几个表的数据。

我的包将截断表,然后插入新数据。当我运行我的ssis包时,由于外键,我得到了一个异常。

我可以禁用约束,运行导入,然后重新启用它们吗?


要禁用外键约束:

1
2
3
4
5
6
7
8
9
10
11
12
13
DECLARE @sql NVARCHAR(MAX) = N'';

;WITH x AS
(
  SELECT DISTINCT obj =
      QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.'
    + QUOTENAME(OBJECT_NAME(parent_object_id))
  FROM sys.foreign_keys
)
SELECT @sql += N'ALTER TABLE ' + obj + ' NOCHECK CONSTRAINT ALL;
' FROM x;

EXEC sp_executesql @sql;

重新启用:

1
2
3
4
5
6
7
8
9
10
11
12
13
DECLARE @sql NVARCHAR(MAX) = N'';

;WITH x AS
(
  SELECT DISTINCT obj =
      QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.'
    + QUOTENAME(OBJECT_NAME(parent_object_id))
  FROM sys.foreign_keys
)
SELECT @sql += N'ALTER TABLE ' + obj + ' WITH CHECK CHECK CONSTRAINT ALL;
' FROM x;

EXEC sp_executesql @sql;

但是,您将无法截断这些表,必须按正确的顺序从中删除。如果需要截断它们,则需要完全删除约束,然后重新创建它们。如果您的外键约束都是简单的单列约束,那么这很简单,但是如果涉及多个列,则更复杂。

这是你可以尝试的东西。为了使其成为SSIS包的一部分,在运行SSIS包时需要一个存储FK定义的位置(您将无法在一个脚本中完成这一切)。因此,在一些实用程序数据库中,创建一个表:

1
CREATE TABLE dbo.PostCommand(cmd NVARCHAR(MAX));

然后,在数据库中,可以有一个执行此操作的存储过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
DELETE other_database.dbo.PostCommand;

DECLARE @sql NVARCHAR(MAX) = N'';

SELECT @sql += N'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(fk.parent_object_id))
   + '.' + QUOTENAME(OBJECT_NAME(fk.parent_object_id))
   + ' ADD CONSTRAINT ' + fk.name + ' FOREIGN KEY ('
   + STUFF((SELECT ',' + c.name
    FROM sys.columns AS c
        INNER JOIN sys.foreign_key_columns AS fkc
        ON fkc.parent_column_id = c.column_id
        AND fkc.parent_object_id = c.[object_id]
    WHERE fkc.constraint_object_id = fk.[object_id]
    ORDER BY fkc.constraint_column_id
    FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, '')
+ ') REFERENCES ' +
QUOTENAME(OBJECT_SCHEMA_NAME(fk.referenced_object_id))
+ '.' + QUOTENAME(OBJECT_NAME(fk.referenced_object_id))
+ '(' +
STUFF((SELECT ',' + c.name
    FROM sys.columns AS c
        INNER JOIN sys.foreign_key_columns AS fkc
        ON fkc.referenced_column_id = c.column_id
        AND fkc.referenced_object_id = c.[object_id]
    WHERE fkc.constraint_object_id = fk.[object_id]
    ORDER BY fkc.constraint_column_id
    FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, '') + ');
' FROM sys.foreign_keys AS fk
WHERE OBJECTPROPERTY(parent_object_id, 'IsMsShipped') = 0;

INSERT other_database.dbo.PostCommand(cmd) SELECT @sql;

IF @@ROWCOUNT = 1
BEGIN
  SET @sql = N'';

  SELECT @sql += N'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(fk.parent_object_id))
    + '.' + QUOTENAME(OBJECT_NAME(fk.parent_object_id))
    + ' DROP CONSTRAINT ' + fk.name + ';
  ' FROM sys.foreign_keys AS fk;

  EXEC sp_executesql @sql;
END

现在,当您的SSIS包完成时,它应该调用另一个存储过程,该过程执行以下操作:

1
2
3
4
5
DECLARE @sql NVARCHAR(MAX);

SELECT @sql = cmd FROM other_database.dbo.PostCommand;

EXEC sp_executesql @sql;

如果你这样做只是为了能够截短而不是删除,我建议你只是点击并运行一个删除。可以使用大容量日志恢复模型来最小化日志的影响。一般来说,我看不出这个解决方案比按正确的顺序使用删除快得多。

2014年,我在这里发表了一篇更详细的文章:

  • 删除并重新创建SQL Server中的所有外键约束


使用内置的sp msforeachtable存储过程。

要禁用所有约束:

1
EXEC sp_msforeachtable"ALTER TABLE ? NOCHECK CONSTRAINT ALL";

要启用所有约束:

1
EXEC sp_msforeachtable"ALTER TABLE ? WITH CHECK CHECK CONSTRAINT ALL";

删除所有表:

1
EXEC sp_msforeachtable"DROP TABLE ?";


请访问:http://msdn.microsoft.com/en-us/magazine/cc163442.aspx。在"禁用所有外键"部分下

从中得到启发,可以通过创建一个临时表并在该表中插入约束,然后删除这些约束,然后从该临时表中重新应用它们来制定方法。我说的够多了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
 SET NOCOUNT ON

    DECLARE @temptable TABLE(
       Id INT PRIMARY KEY IDENTITY(1, 1),
       FKConstraintName VARCHAR(255),
       FKConstraintTableSchema VARCHAR(255),
       FKConstraintTableName VARCHAR(255),
       FKConstraintColumnName VARCHAR(255),
       PKConstraintName VARCHAR(255),
       PKConstraintTableSchema VARCHAR(255),
       PKConstraintTableName VARCHAR(255),
       PKConstraintColumnName VARCHAR(255)    
    )

    INSERT INTO @temptable(FKConstraintName, FKConstraintTableSchema, FKConstraintTableName, FKConstraintColumnName)
    SELECT
       KeyColumnUsage.CONSTRAINT_NAME,
       KeyColumnUsage.TABLE_SCHEMA,
       KeyColumnUsage.TABLE_NAME,
       KeyColumnUsage.COLUMN_NAME
    FROM
       INFORMATION_SCHEMA.KEY_COLUMN_USAGE KeyColumnUsage
          INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TableConstraints
             ON KeyColumnUsage.CONSTRAINT_NAME = TableConstraints.CONSTRAINT_NAME
    WHERE
       TableConstraints.CONSTRAINT_TYPE = 'FOREIGN KEY'

    UPDATE @temptable SET
       PKConstraintName = UNIQUE_CONSTRAINT_NAME
    FROM
       @temptable tt
          INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS ReferentialConstraint
             ON tt.FKConstraintName = ReferentialConstraint.CONSTRAINT_NAME

    UPDATE @temptable SET
       PKConstraintTableSchema  = TABLE_SCHEMA,
       PKConstraintTableName  = TABLE_NAME
    FROM @temptable tt
       INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TableConstraints
          ON tt.PKConstraintName = TableConstraints.CONSTRAINT_NAME

    UPDATE @temptable SET
       PKConstraintColumnName = COLUMN_NAME
    FROM @temptable tt
       INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KeyColumnUsage
          ON tt.PKConstraintName = KeyColumnUsage.CONSTRAINT_NAME


    --Now to drop constraint:
    SELECT
       '
       ALTER TABLE [' + FKConstraintTableSchema + '].[' + FKConstraintTableName + ']
       DROP CONSTRAINT ' + FKConstraintName + '

       GO'
    FROM
       @temptable

    --Finally to add constraint:
    SELECT
       '
       ALTER TABLE [' + FKConstraintTableSchema + '].[' + FKConstraintTableName + ']
       ADD CONSTRAINT ' + FKConstraintName + ' FOREIGN KEY(' + FKConstraintColumnName + ') REFERENCES [' + PKConstraintTableSchema + '].[' + PKConstraintTableName + '](' + PKConstraintColumnName + ')

       GO'
    FROM
       @temptable

    GO


禁用所有表约束

1
ALTER TABLE TableName NOCHECK CONSTRAINT ConstraintName

--启用所有表约束

1
ALTER TABLE TableName CHECK CONSTRAINT ConstraintName


如果使用的数据库架构与".dbo"不同,或者数据库中包含由多个字段组成的pk,请不要使用carter medlin的解决方案,否则会损坏数据库!!!!

使用不同的模式时,请尝试此操作(不要忘记以前备份数据库!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DECLARE @sql AS NVARCHAR(max)=''
select @sql = @sql +
    'ALTER INDEX ALL ON ' + SCHEMA_NAME( t.schema_id) +'.'+ '['+ t.[name] + '] DISABLE;'+CHAR(13)
from  
    sys.tables t
where type='u'

select @sql = @sql +
    'ALTER INDEX ' + i.[name] + ' ON ' + SCHEMA_NAME( t.schema_id) +'.'+'[' + t.[name] + '] REBUILD;'+CHAR(13)
from  
    sys.key_constraints i
join
    sys.tables t on i.parent_object_id=t.object_id
where     i.type='PK'

exec dbo.sp_executesql @sql;
go

在执行了一些自由FK操作后,您可以使用

1
2
3
4
5
6
7
8
9
10
DECLARE @sql AS NVARCHAR(max)=''
select @sql = @sql +
    'ALTER INDEX ALL ON ' + SCHEMA_NAME( t.schema_id) +'.'+'[' +  t.[name] + '] REBUILD;'+CHAR(13)
from  
    sys.tables t
where type='u'
print @sql

exec dbo.sp_executesql @sql;
exec sp_msforeachtable"ALTER TABLE ? WITH NOCHECK CHECK CONSTRAINT ALL";


即使禁用外键,也无法截断表。delete命令删除表中的所有记录,但如果使用delete,请注意对于包含数百万条记录的表的命令,则包将变慢。您的事务日志大小将会增加,可能会填满您宝贵的磁盘空间。

如果您删除约束,可能会出现不干净的数据填满您的表的情况。当您试图重新创建约束时,它可能不允许您这样做,因为它会给出错误。因此,请确保如果删除约束,则正在加载彼此正确相关的数据,并满足要重新创建的约束关系。

因此,请仔细考虑每种方法的优缺点,并根据您的要求使用。


不需要在SQL上运行对Sidable FK的查询。如果您有从表A到表B的FK,您应该:

  • 从表A中删除数据
  • 从表B中删除数据
  • 在B上插入数据
  • 在上插入数据

您还可以告诉目的地不要检查约束

enter image description here


禁用所有索引(包括将禁用所有FK的pk),然后重新启用pk。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DECLARE @sql AS NVARCHAR(max)=''
select @sql = @sql +
    'ALTER INDEX ALL ON [' + t.[name] + '] DISABLE;'+CHAR(13)
from  
    sys.tables t
where type='u'

select @sql = @sql +
    'ALTER INDEX ' + i.[name] + ' ON [' + t.[name] + '] REBUILD;'+CHAR(13)
from  
    sys.key_constraints i
join
    sys.tables t on i.parent_object_id=t.object_id
where
    i.type='PK'


exec dbo.sp_executesql @sql;
go

[加载数据]

然后把所有的东西都复活…

1
2
3
4
5
6
7
8
9
DECLARE @sql AS NVARCHAR(max)=''
select @sql = @sql +
    'ALTER INDEX ALL ON [' + t.[name] + '] REBUILD;'+CHAR(13)
from  
    sys.tables t
where type='u'

exec dbo.sp_executesql @sql;
go