SQL Server支持禁用和启用外键约束吗?还是我唯一的选择就是放弃这些限制,然后重新创造这些限制?


当前回答

如果你想禁用数据库中的所有约束,只需运行以下代码:

-- disable all constraints
EXEC sp_MSforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"

要将它们重新打开,运行:(打印当然是可选的,它只是列出表)

-- enable all constraints
exec sp_MSforeachtable @command1="print '?'", @command2="ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all"

我发现它在将数据从一个数据库填充到另一个数据库时很有用。这是比放弃约束更好的方法。正如您所提到的,当删除数据库中的所有数据并重新填充它时(例如在测试环境中),它非常方便。

如果您正在删除所有数据,您可能会发现这个解决方案是有用的。

有时禁用所有触发器也很方便,你可以在这里看到完整的解决方案。

其他回答

SQL-92标准允许将约束声明为DEFERRABLE,以便在事务范围内(隐式或显式)延迟它。遗憾的是,SQL Server仍然缺少这个SQL-92功能。

对我来说,将约束更改为NOCHECK类似于动态更改数据库结构——删除约束当然是这样——这是需要避免的(例如,用户需要增加特权)。

几乎可以肯定,检查是必须的!

在一些回答和评论中提出了这一点,但我认为再次提出这一点非常重要。

使用以下命令(不使用WITH CHECK)重新启用约束会有一些严重的缺陷。

ALTER TABLE MyTable CHECK CONSTRAINT MyConstraint;

WITH CHECK | WITH NOCHECK Specifies whether the data in the table is or is not validated against a newly added or re-enabled FOREIGN KEY or CHECK constraint. If not specified, WITH CHECK is assumed for new constraints, and WITH NOCHECK is assumed for re-enabled constraints. If you do not want to verify new CHECK or FOREIGN KEY constraints against existing data, use WITH NOCHECK. We do not recommend doing this, except in rare cases. The new constraint will be evaluated in all later data updates. Any constraint violations that are suppressed by WITH NOCHECK when the constraint is added may cause future updates to fail if they update rows with data that does not comply with the constraint. The query optimizer does not consider constraints that are defined WITH NOCHECK. Such constraints are ignored until they are re-enabled by using ALTER TABLE table WITH CHECK CHECK CONSTRAINT ALL.

注意:WITH NOCHECK是重新启用约束的默认值。我想知道为什么……

No existing data in the table will be evaluated during the execution of this command - successful completion is no guarantee that the data in the table is valid according to the constraint. During the next update of the invalid records, the constraint will be evaluated and will fail - resulting in errors that may be unrelated to the actual update that is made. Application logic that relies on the constraint to ensure that data is valid may fail. The query optimizer will not make use of any constraint that is enabled in this way.

sys。Foreign_keys系统视图提供了一些问题的可见性。注意,它有一个is_disabled列和一个is_not_trusted列。Is_disabled指示是否根据约束验证未来的数据操作。Is_not_trusted指示表中当前的所有数据是否已经根据约束进行了验证。

ALTER TABLE MyTable WITH CHECK CHECK CONSTRAINT MyConstraint;

你的约束是可信的吗?找出……

SELECT * FROM sys.foreign_keys WHERE is_not_trusted = 1;

如果你想禁用数据库中的所有约束,只需运行以下代码:

-- disable all constraints
EXEC sp_MSforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"

要将它们重新打开,运行:(打印当然是可选的,它只是列出表)

-- enable all constraints
exec sp_MSforeachtable @command1="print '?'", @command2="ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all"

我发现它在将数据从一个数据库填充到另一个数据库时很有用。这是比放弃约束更好的方法。正如您所提到的,当删除数据库中的所有数据并重新填充它时(例如在测试环境中),它非常方便。

如果您正在删除所有数据,您可能会发现这个解决方案是有用的。

有时禁用所有触发器也很方便,你可以在这里看到完整的解决方案。

要禁用约束,您可以使用NOCHECK更改表

ALTER TABLE [TABLE_NAME] NOCHECK CONSTRAINT [ALL|CONSTRAINT_NAME]

要使您必须使用双重检查:

ALTER TABLE [TABLE_NAME] WITH CHECK CHECK CONSTRAINT [ALL|CONSTRAINT_NAME]

启用时请注意双重CHECK CHECK。 ALL表示表中的所有约束。

一旦完成,如果您需要检查状态,使用这个脚本列出约束状态。会很有帮助:

    SELECT (CASE 
        WHEN OBJECTPROPERTY(CONSTID, 'CNSTISDISABLED') = 0 THEN 'ENABLED'
        ELSE 'DISABLED'
        END) AS STATUS,
        OBJECT_NAME(CONSTID) AS CONSTRAINT_NAME,
        OBJECT_NAME(FKEYID) AS TABLE_NAME,
        COL_NAME(FKEYID, FKEY) AS COLUMN_NAME,
        OBJECT_NAME(RKEYID) AS REFERENCED_TABLE_NAME,
        COL_NAME(RKEYID, RKEY) AS REFERENCED_COLUMN_NAME
   FROM SYSFOREIGNKEYS
ORDER BY TABLE_NAME, CONSTRAINT_NAME,REFERENCED_TABLE_NAME, KEYNO 

您可以暂时禁用表上的约束,进行工作,然后重新构建它们。

这里有一个简单的方法…

禁用所有索引,包括主键,这将禁用所有外键,然后重新启用主键,以便您可以使用它们…

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

[做一些事情,比如加载数据]

然后重新启用和重建索引……

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