SQLSERVER清空(Truncate)被外键引用的数据表
前言:我们知道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
结束语:文章无甚深浅,止乎于分享。如有错误,还望斧正。