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
View Code

如何使用

例子说明:清空整个数据库

 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
View Code

 

结束语:文章无甚深浅,止乎于分享。如有错误,还望斧正。

posted @ 2013-11-27 14:35  i迷倪  阅读(8203)  评论(2编辑  收藏  举报