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

 

結束語:文章無甚深淺,止乎於分享。如有錯誤,還望斧正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM