【问题标题】:Disabling foreign key constraint, still can't truncate table? (SQL Server 2005)禁用外键约束,还是不能截断表? (SQL Server 2005)
【发布时间】:2011-04-20 02:50:18
【问题描述】:

我有一个名为 PX_Child 的表,它在 PX_Parent 上有一个外键。我想暂时禁用这个 FK 约束,以便截断 PX_Parent。我不确定这是怎么回事。

我已经尝试过这些命令

ALTER TABLE PX_Child NOCHECK CONSTRAINT ALL

ALTER TABLE PX_Parent NOCHECK CONSTRAINT ALL

(truncate commands)

ALTER TABLE PX_Child CHECK CONSTRAINT ALL

ALTER TABLE PX_Parent CHECK CONSTRAINT ALL

但是截断仍然告诉我它不能截断 PX_Parent 因为外键约束。我环顾了整个网络,似乎找不到我做错了什么,抱歉这个问题的基本性质。

【问题讨论】:

  • 看起来 Kalen Delaney 无意中促成了这个想法。 Here she clarifies "你必须删除引用约束才能截断表。"

标签: sql-server foreign-keys constraints


【解决方案1】:

如果有任何外键引用它,您不能截断该表,包括禁用的约束。您要么需要删除外键约束,要么使用DELETE 命令。

【讨论】:

  • 请参阅下面的(5 年后)答案,了解如何快速生成 DROP CONSTRAINTADD CONSTRAINT SQL
  • DELETE 语句与 REFERENCE 约束冲突。 (删除也不行)
【解决方案2】:

SQL 服务器不允许您在约束存在时截断表,即使它已被禁用。 删除约束并在截断表后重新创建它。 或者只是删除并重新创建表,这在您的应用程序中更容易做到。

【讨论】:

  • 不是事务性命令是什么意思?你可以把它回滚就好了。 CREATE TABLE Blah(a int); INSERT Blah VALUES(1); SELECT * FROM Blah; BEGIN TRAN; TRUNCATE TABLE Blah; SELECT * FROM Blah; ROLLBACK TRAN SELECT * FROM Blah; DROP TABLE Blah。截断通过释放整个页面而不是删除行来工作,但它仍然是事务性的。
  • @Emtucifor:哎呀,好像我误解了文档,你是对的!我删除了那条错误信息。
  • @Emtucifor,@pgroke,在某种程度上,你们都是正确的,因为标准允许 TRUNCATE 是非事务性的,但允许实现使其成为事务性。因此,定义的 TRUNCATE 不承诺可以进行回滚,但 SqlServer(和 Postgres)在标准之外添加了该承诺。
  • @Jon 感谢您的澄清。让我重述一下。 在 SQL Server 中,截断是事务性的。
【解决方案3】:

有一种更简单的方法。我遇到了同样的问题并找到了这个解决方案: https://www.mssqltips.com/sqlservertip/3347/drop-and-recreate-all-foreign-key-constraints-in-sql-server/

如果您只是在数据库中运行此查询,它将生成您需要在存储过程之前/之后包含的 T-SQL,以便删除然后恢复任何外键约束。

不要担心试图理解这个查询本身。

CREATE TABLE #x -- feel free to use a permanent table
(
  drop_script NVARCHAR(MAX),
  create_script NVARCHAR(MAX)
);

DECLARE @drop   NVARCHAR(MAX) = N'',
        @create NVARCHAR(MAX) = N'';

-- drop is easy, just build a simple concatenated list from sys.foreign_keys:
SELECT @drop += N'
ALTER TABLE ' + QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name) 
    + ' DROP CONSTRAINT ' + QUOTENAME(fk.name) + ';'
FROM sys.foreign_keys AS fk
INNER JOIN sys.tables AS ct
  ON fk.parent_object_id = ct.[object_id]
INNER JOIN sys.schemas AS cs 
  ON ct.[schema_id] = cs.[schema_id];

INSERT #x(drop_script) SELECT @drop;

-- create is a little more complex. We need to generate the list of 
-- columns on both sides of the constraint, even though in most cases
-- there is only one column.
SELECT @create += N'
ALTER TABLE ' 
   + QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name) 
   + ' ADD CONSTRAINT ' + QUOTENAME(fk.name) 
   + ' FOREIGN KEY (' + STUFF((SELECT ',' + QUOTENAME(c.name)
   -- get all the columns in the constraint table
    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(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'')
  + ') REFERENCES ' + QUOTENAME(rs.name) + '.' + QUOTENAME(rt.name)
  + '(' + STUFF((SELECT ',' + QUOTENAME(c.name)
   -- get all the referenced columns
    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(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') + ');'
FROM sys.foreign_keys AS fk
INNER JOIN sys.tables AS rt -- referenced table
  ON fk.referenced_object_id = rt.[object_id]
INNER JOIN sys.schemas AS rs 
  ON rt.[schema_id] = rs.[schema_id]
INNER JOIN sys.tables AS ct -- constraint table
  ON fk.parent_object_id = ct.[object_id]
INNER JOIN sys.schemas AS cs 
  ON ct.[schema_id] = cs.[schema_id]
WHERE rt.is_ms_shipped = 0 AND ct.is_ms_shipped = 0;

UPDATE #x SET create_script = @create;

PRINT @drop;
PRINT @create;

/*
EXEC sp_executesql @drop
-- clear out data etc. here
EXEC sp_executesql @create;
*/

生成一堆:

ALTER TABLE [dbo].[Whatever] DROP CONSTRAINT....
--
ALTER TABLE [dbo].[Whatever] ADD CONSTRAINT....

【讨论】:

  • 不好的建议:“不要担心试图理解这个查询本身”。永远不要在不理解的情况下运行任何从网上获得的东西
  • 了解查询的工作原理和确保它不会造成任何伤害之间是有区别的。后者几乎总是更容易。
  • 为了简单地执行 TRUNCATE TABLE 需要这种混乱的动态 sql,这一事实令人反感。
  • 正如所写,这并没有正确地重新创建具有“ON DELETE CASCADE”规则的约束。这是一个简单的修复,将行 FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') + ');' 更改为 FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') + ') ON DELETE ' + REPLACE(fk.delete_referential_action_desc, '_', ' ') + ';'
  • 请注意,PRINT @dropPRINT @create 语句将在 SQL Management Studio 中截断。如果您想要这些完整值,您需要SELECT * FROM #x,然后从两个结果列中复制值。即便如此,我最终还是不得不 DECLARE @tempCreate NVARCHAR(MAX); SELECT @tempCreate = create_script from #x; EXEC sp_executesql @tempCreate; 创建我的声明。
【解决方案4】:

没有这样的选项可以在外键约束时截断表,但我们可以使用一些技巧,例如

 ALTER TABLE [dbo].[table2] DROP CONSTRAINT [FK_table2_table1]
    GO
    truncate table [table1]
GO
    ALTER TABLE [dbo].[table2]  WITH CHECK ADD  CONSTRAINT [FK_table2_table1] FOREIGN KEY([FKId])
    REFERENCES [dbo].[table1] ([ID])
    GO

    ALTER TABLE [dbo].[table2] CHECK CONSTRAINT [FK_table2_table1]
    GO

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-04
    • 1970-01-01
    • 2011-07-24
    • 2016-07-28
    • 1970-01-01
    • 2017-06-17
    相关资源
    最近更新 更多