前言:我們知道SQLSERVER清空數據表有兩種方式Delete和Truncate,當然兩者的不同大家也都知道(不清楚的可以MSDN)。不過這個錯誤“Cannot truncate table because it is being referenced by a FOREIGN KEY” 相信大家也都遇到過,解決的已解決,未解決的且看下文。
如何解決
開始我以為只要將外鍵Disable掉就可以了,事實證明是沒用的。其實MSDN已經明確告訴了我們:
不能對以下表使用 TRUNCATE TABLE:
- 由 FOREIGN KEY 約束引用的表。(您可以截斷具有引用自身的外鍵的表。)
- 參與索引視圖的表。
- 通過使用事務復制或合並復制發布的表。
對於具有以上一個或多個特征的表,請使用 DELETE 語句。
TRUNCATE TABLE 不能激活觸發器,因為該操作不記錄各個行刪除
難道我真的要用Delete嗎?可我真的不想用Delete。原因就在於Truncate的優點,MSDN說:
與 DELETE 語句相比,TRUNCATE TABLE 具有以下優點:
- 所用的事務日志空間較少。
DELETE 語句每次刪除一行,並在事務日志中為所刪除的每行記錄一個項。TRUNCATE TABLE 通過釋放用於存儲表數據的數據頁來刪除數據,並且在事務日志中只記錄頁釋放。- 使用的鎖通常較少。
當使用行鎖執行 DELETE 語句時,將鎖定表中各行以便刪除。TRUNCATE TABLE 始終鎖定表和頁,而不是鎖定各行。- 如無例外,在表中不會留有任何頁。
執行 DELETE 語句后,表仍會包含空頁。(略去例如)
好了,下面就來說一下解決方法。
解決方案
1.使用Delete
a) 先Delete依賴表(或叫從表)
b) 再Delete被依賴表(或叫主表)
2.使用Truncate
a) 先備份依賴表外鍵
b) 刪除依賴表外鍵
c) Truncate主表
d) 重新創建依賴表外鍵
一段腳本
其實是一個使用Truncate進行處理的存儲過程,思路見上。

1 USE <YOUR DB> 2 GO 3 4 CREATE PROCEDURE [dbo].[usp_Truncate_Table] 5 @TableToTruncate VARCHAR(64) 6 AS 7 8 BEGIN 9 10 SET NOCOUNT ON 11 12 --==變量定義 13 DECLARE @i int 14 DECLARE @Debug bit 15 DECLARE @Recycle bit 16 DECLARE @Verbose bit 17 DECLARE @TableName varchar(80) 18 DECLARE @ColumnName varchar(80) 19 DECLARE @ReferencedTableName varchar(80) 20 DECLARE @ReferencedColumnName varchar(80) 21 DECLARE @ConstraintName varchar(250) 22 23 DECLARE @CreateStatement varchar(max) 24 DECLARE @DropStatement varchar(max) 25 DECLARE @TruncateStatement varchar(max) 26 DECLARE @CreateStatementTemp varchar(max) 27 DECLARE @DropStatementTemp varchar(max) 28 DECLARE @TruncateStatementTemp varchar(max) 29 DECLARE @Statement varchar(max) 30 31 SET @Debug = 0--(0:將執行相關語句|1:不執行語句) 32 SET @Recycle = 0--(0:不創建/不清除存儲表|1:將創建/清理存儲表) 33 set @Verbose = 1--(1:每步執行均打印消息|0:不打印消息) 34 35 SET @i = 1 36 SET @CreateStatement = 'ALTER TABLE [dbo].[<tablename>] WITH NOCHECK ADD CONSTRAINT [<constraintname>] FOREIGN KEY([<column>]) REFERENCES [dbo].[<reftable>] ([<refcolumn>])' 37 SET @DropStatement = 'ALTER TABLE [dbo].[<tablename>] DROP CONSTRAINT [<constraintname>]' 38 SET @TruncateStatement = 'TRUNCATE TABLE [<tablename>]' 39 40 -- 創建外鍵臨時表 41 IF OBJECT_ID('tempdb..#FKs') IS NOT NULL 42 DROP TABLE #FKs 43 44 -- 獲取外鍵 45 SELECT ROW_NUMBER() OVER (ORDER BY OBJECT_NAME(parent_object_id), clm1.name) as ID, 46 OBJECT_NAME(constraint_object_id) as ConstraintName, 47 OBJECT_NAME(parent_object_id) as TableName, 48 clm1.name as ColumnName, 49 OBJECT_NAME(referenced_object_id) as ReferencedTableName, 50 clm2.name as ReferencedColumnName 51 INTO #FKs 52 FROM sys.foreign_key_columns fk 53 JOIN sys.columns clm1 ON fk.parent_column_id = clm1.column_id AND fk.parent_object_id = clm1.object_id 54 JOIN sys.columns clm2 ON fk.referenced_column_id = clm2.column_id AND fk.referenced_object_id= clm2.object_id 55 --WHERE OBJECT_NAME(parent_object_id) not in ('//tables that you do not wont to be truncated') 56 WHERE OBJECT_NAME(referenced_object_id) = @TableToTruncate 57 ORDER BY OBJECT_NAME(parent_object_id) 58 59 -- 外鍵操作(刪除|重建)表 60 IF Not EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Internal_FK_Definition_Storage') 61 BEGIN 62 IF @Verbose = 1 63 PRINT '1. 正在創建表(Internal_FK_Definition_Storage)...' 64 CREATE TABLE [Internal_FK_Definition_Storage] 65 ( 66 ID int not null identity(1,1) primary key, 67 FK_Name varchar(250) not null, 68 FK_CreationStatement varchar(max) not null, 69 FK_DestructionStatement varchar(max) not null, 70 Table_TruncationStatement varchar(max) not null 71 ) 72 END 73 ELSE 74 BEGIN 75 IF @Recycle = 0 76 BEGIN 77 IF @Verbose = 1 78 PRINT '1. 正在清理表(Internal_FK_Definition_Storage)...' 79 TRUNCATE TABLE [Internal_FK_Definition_Storage] 80 END 81 ELSE 82 PRINT '1. 正在清理表(Internal_FK_Definition_Storage)...' 83 END 84 85 IF @Recycle = 0 86 BEGIN 87 IF @Verbose = 1 88 PRINT '2. 正在備份外鍵定義...' 89 WHILE (@i <= (SELECT MAX(ID) FROM #FKs)) 90 BEGIN 91 SET @ConstraintName = (SELECT ConstraintName FROM #FKs WHERE ID = @i) 92 SET @TableName = (SELECT TableName FROM #FKs WHERE ID = @i) 93 SET @ColumnName = (SELECT ColumnName FROM #FKs WHERE ID = @i) 94 SET @ReferencedTableName = (SELECT ReferencedTableName FROM #FKs WHERE ID = @i) 95 SET @ReferencedColumnName = (SELECT ReferencedColumnName FROM #FKs WHERE ID = @i) 96 97 SET @DropStatementTemp = REPLACE(REPLACE(@DropStatement,'<tablename>',@TableName),'<constraintname>',@ConstraintName) 98 SET @CreateStatementTemp = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@CreateStatement,'<tablename>',@TableName),'<column>',@ColumnName),'<constraintname>',@ConstraintName),'<reftable>',@ReferencedTableName),'<refcolumn>',@ReferencedColumnName) 99 SET @TruncateStatementTemp = REPLACE(@TruncateStatement,'<tablename>',@TableName) 100 101 INSERT INTO [Internal_FK_Definition_Storage] 102 SELECT @ConstraintName, @CreateStatementTemp, @DropStatementTemp, @TruncateStatementTemp 103 104 SET @i = @i + 1 105 106 IF @Verbose = 1 107 PRINT ' > 已備份外鍵:[' + @ConstraintName + '] 所屬表: [' + @TableName + ']' 108 END 109 END 110 ELSE 111 PRINT '2. 正在備份外鍵定義...' 112 113 IF @Verbose = 1 114 PRINT '3. 正在刪除外鍵...' 115 BEGIN TRAN 116 BEGIN TRY 117 SET @i = 1 118 WHILE (@i <= (SELECT MAX(ID) FROM [Internal_FK_Definition_Storage])) 119 BEGIN 120 SET @ConstraintName = (SELECT FK_Name FROM [Internal_FK_Definition_Storage] WHERE ID = @i) 121 SET @Statement = (SELECT FK_DestructionStatement FROM [Internal_FK_Definition_Storage] WITH (NOLOCK) WHERE ID = @i) 122 IF @Debug = 1 123 PRINT @Statement 124 ELSE 125 EXEC(@Statement) 126 SET @i = @i + 1 127 IF @Verbose = 1 128 PRINT ' > 已刪除外鍵:[' + @ConstraintName + ']' 129 END 130 131 IF @Verbose = 1 132 PRINT '4. 正在清理數據表...' 133 --先清除該外鍵所在表(由於外鍵所在表仍可能又被其他外鍵所引用,因此需要循環遞歸處理)(注:本處理未實現) 134 --請不要使用下面注釋代碼 135 /* 136 SET @i = 1 137 WHILE (@i <= (SELECT MAX(ID) FROM [Internal_FK_Definition_Storage])) 138 BEGIN 139 SET @Statement = (SELECT Table_TruncationStatement FROM [Internal_FK_Definition_Storage] WHERE ID = @i) 140 IF @Debug = 1 141 PRINT @Statement 142 ELSE 143 EXEC(@Statement) 144 SET @i = @i + 1 145 IF @Verbose = 1 146 PRINT ' > ' + @Statement 147 END 148 */ 149 150 IF @Debug = 1 151 PRINT 'TRUNCATE TABLE [' + @TableToTruncate + ']' 152 ELSE 153 EXEC('TRUNCATE TABLE [' + @TableToTruncate + ']') 154 IF @Verbose = 1 155 PRINT ' > 已清理數據表[' + @TableToTruncate + ']' 156 157 IF @Verbose = 1 158 PRINT '5. 正在重建外鍵...' 159 SET @i = 1 160 WHILE (@i <= (SELECT MAX(ID) FROM [Internal_FK_Definition_Storage])) 161 BEGIN 162 SET @ConstraintName = (SELECT FK_Name FROM [Internal_FK_Definition_Storage] WHERE ID = @i) 163 SET @Statement = (SELECT FK_CreationStatement FROM [Internal_FK_Definition_Storage] WHERE ID = @i) 164 IF @Debug = 1 165 PRINT @Statement 166 ELSE 167 EXEC(@Statement) 168 SET @i = @i + 1 169 IF @Verbose = 1 170 PRINT ' > 已重建外鍵:[' + @ConstraintName + ']' 171 END 172 COMMIT 173 END TRY 174 BEGIN CATCH 175 ROLLBACK 176 PRINT '出錯信息:'+ERROR_MESSAGE() 177 END CATCH 178 IF @Verbose = 1 179 PRINT '6. 處理完成!' 180 END
如何使用
例子說明:清空整個數據庫

1 USE <YOUR DB> 2 GO 3 4 --==創建臨時表 5 IF(OBJECT_ID('TEMPDB..#TEMP')IS NOT NULL) 6 DROP TABLE #TEMP 7 8 --==讀取數據庫表 9 SELECT SN=ROW_NUMBER()OVER(ORDER BY [name]ASC),TableName=[name] 10 INTO #TEMP 11 FROM sys.tables 12 WHERE [name]<>'Internal_FK_Definition_Storage' 13 14 --SELECT * FROM #TEMP 15 16 --==開始處理 17 DECLARE @ROWS INT 18 SELECT @ROWS=MAX(SN)FROM #TEMP 19 DECLARE @I INT 20 SET @I=1 21 DECLARE @TableName VARCHAR(64) 22 WHILE(@I<=@ROWS) 23 BEGIN 24 IF(EXISTS(SELECT 1 FROM #TEMP WHERE SN=@I)) 25 BEGIN 26 SELECT @TableName=TableName FROM #TEMP WHERE SN=@I 27 EXEC [dbo].[usp_Truncate_Table] @TableToTruncate = @TableName 28 END 29 SET @TableName=N'' 30 SET @I=@I+1 31 END
結束語:文章無甚深淺,止乎於分享。如有錯誤,還望斧正。