By: Jeffrey Yao | Updated: 2016-04-18 | Comments (4) | Related: > PowerShell
Problem
When working with SQL Server databases, sometimes there is a need to remove all data inside the database. Is there any way that I can do this efficiently especially when some of my tables can be very large?
Solution
The solution seems as simple as deleting data from tables (in a specified order if foreign key constraints exist), but there are a few challenges:
- When you have big tables, using "delete table" can take a long time and generate huge transaction logs.
- We can use "truncate table", but if a table is referenced, you cannot use truncate table even the referring table is empty
- Also "Truncate table" cannot truncate a table that is schema bound by views or UDFs
You will see a few solutions mentioned in the Next Steps section, but for TRUNCATE table solutions, none is complete in considering that tables may be schema bounded by views and UDFs as mentioned in this article.
The solution can be a pure T-SQL solution, but it would be pretty lengthy with eye-sore dynamic code. As such, I will use PowerShell to address the above challenges, so here are the detailed steps (we assume the database is standalone, i.e. not involved in replication or mirroring):
- Script out all Foreign Keys and save the script in a string variable
- Drop the Foreign Keys
- If table is schema bound, then we script out the table into a variable and create a duplicate table (with different name) and use "alter table ... switch to ..." to move the original table's data to the newly created table, then we drop the newly created table.
- If the table is not schema bound, then we just truncate the table.
- Repeat from step 2 until all tables are processed
- Then recreate the Foreign Keys
For details about truncating tables and the challenges, please read Solution to T-SQL Table Truncation Challenge.
Prepare Test Environment
I used a backup of AdventureWorks 2012 and restored it to a new database named [AW2012].
Also as mentioned above, we assume [AW2012] is a single standalone database, with no replication, mirroring, or HA settings.
I only tested the code in SQL Server 2012, but I am sure it applies to SQL Server 2005+ versions.
Source Code
I assume you have installed SQL Server 2012 with the SQLPS module.
- Start a PowerShell ISE window, then copy and paste the code into the window.
- Change the first three variables, $mach, $sql_instance and $DBname to match your environment
- Run the script
# This script is to truncate all tables in a database # Author: Jeffrey Yao | 2016/03/15 # requires -version 3.0 import-module sqlps -DisableNameChecking; set-location c:\; # change the following three variables according to your environment needs [string]$mach = $env:COMPUTERNAME; [string]$sql_instance = 'default'; [string]$DBname = 'AW2012'; $svr = get-item "sqlserver:\sql\$mach\$sql_instance" [String]$FT_index=''; [Microsoft.SqlServer.Management.Smo.Database]$db = get-item "sqlserver:\sql\$mach\$sql_instance\databases\$($DBname)"; # AW2012 # This script is to truncate all tables in a database # Author: Jeffrey Yao | 2016/03/15 import-module sqlps -DisableNameChecking; set-location c:\; # change the following three variables according to your environment needs [string]$mach = $env:COMPUTERNAME; [string]$sql_instance = 'default'; [string]$DBname = 'AW2012'; $svr = get-item "sqlserver:\sql\$mach\$sql_instance" [String]$FT_index=''; [Microsoft.SqlServer.Management.Smo.Database]$db = get-item "sqlserver:\sql\$mach\$sql_instance\databases\$($DBname)"; # AW2012 $db.tables.Refresh(); #script out FKs and save it to variable $fk_script $db.tables | % -begin {[string]$fk_script=''} -process {$_.foreignkeys.refresh(); $_.foreignkeys | % {$fk_script +=$_.script() +";`r`n"} } #drop foreign keys $db.tables | % -begin {$fks=@()} -process { $fks += $_.ForeignKeys }; foreach ($fk in $fks) {$fk.drop();} $spt = new-object -TypeName "microsoft.sqlserver.management.smo.scripter" -ArgumentList $svr; $spt.Options.WithDependencies = $true; $so = new-object "microsoft.sqlserver.management.smo.scriptingoptions"; $so.DriPrimaryKey = $true; $so.DriIndexes = $true; $so.Indexes=$false; $so.FullTextIndexes = $false; foreach ($t in $db.tables ) { #we will check whether this table isSchemaBound by views/udfs #if yes, we will need to create a duplicate table with different table names, constraint names #and then use "alter table ... switch .. " to do the trunation work #otherwise, we can simple do a truncate table $t.refresh(); $dpt = $spt.DiscoverDependencies($t, $false); $collection=$spt.WalkDependencies($dpt); if ($collection.count -gt 1) { if ($t.FullTextIndex -ne $null) # if there is FT index, we will drop it first { $t.FullTextIndex | % -Begin {$FTx=@();} -process {$FT_index += $_.script(); $FTx +=$_;} foreach($f in $FTx) {$f.drop();} } [string]$ts=$t.script($so) -join "`r`n"; $t.indexes | ? { ! ($_.isXmlIndex -or $_.isSpatialIndex -or ($_.IndexKeyType -eq 'DRIPrimaryKey'))} | % {$ts +=$_.script() + "`r`n"} $ts = $ts -replace "\sCONSTRAINT \[(\w+)]\s", ' CONSTRAINT [$1_x] '; # you must use the signle quote here $ts = $ts -replace "\sINDEX \[(\w+)]\s", ' INDEX [$1_x] '; $ts = $ts -replace "\[$($t.schema)]\.\[$($t.name)]", "[$($t.schema)].[$($t.name)_x]"; try { $db.ExecuteNonQuery($ts); $qry = "alter table [$($t.schema)].[$($t.name)] switch to [$($t.schema)].[$($t.name)_x];" $qry; $db.ExecuteNonQuery($qry); $qry = "drop table [$($t.schema)].[$($t.name)_x];" $db.ExecuteNonQuery($qry); #add back the FullText Index if ($FT_index.Length -gt 0) { $db.ExecuteNonQuery($FT_index); $FT_index = '';} } catch { write-host "error running: $qry" -ForegroundColor Red; } } #$colletion.count -gt 1 else { $t.TruncateData(); #direct truncate write-host "truncate table [$($t.Schema)].[$($t.name)]" -ForegroundColor yellow } } # resetup the FKs $db.ExecuteNonQuery($fk_script); $db = $null; $svr = $null;
Before running the PowerShell script, we can run the following code before and after to get a picture of the number of rows in the table.
use AW2012 select top 10 [Table]=name, Rows from sys.tables t inner join sys.partitions p on t.object_id = p.object_id and p.index_id < 2 order by 2 desc
Here are the results. The left side is before the PowerShell script is run and the right side is after the PowerShell script is run. You can see that all of the tables now have 0 rows.
Next Steps
- You can change this PowerShell script to a function, so you can call it like a cmdlet
- Read these related articles:
About the author
This author pledges the content of this article is based on professional experience and not AI generated.
View all my tips
Article Last Updated: 2016-04-18