Truncate all tables in a SQL Server database

By:   |   Comments (17)   |   Related: > Constraints


Problem

As a DBA I have found many occasions in testing of various SQL Server deployments and scripts where I need to load a database with data from a lower environment or where I need to alter a table that might already have data in it in such a way that I need to first eliminate the data before I can proceed. Depending on the foreign key constraints in place, clearing old data can be a tedious process.

Solution

When I don't care about existing data in a SQL Server database, but I don't want to resort to dropping and re-creating the database and all the additional tasks that come with it, I have found truncating all the data in all the tables to be an effective solution. I prefer truncating over deleting the data because of the various advantages truncating has by nature when you want to reset to a clean state.

Unfortunately, in most complex database designs there are numerous foreign key relationships that make it impossible to just use a simple "for each" loop and issuing a TRUNCATE command.

To illustrate this problem and my solution, we first need to create a database with several tables with foreign key constraints.

CREATE DATABASE ForeignKeyTest
GO
USE ForeignKeyTest
GO
CREATE TABLE T1
       (
        T1Id INT IDENTITY
                 PRIMARY KEY
                 NOT NULL
       ,Customer VARCHAR(50) NOT NULL
       )
GO
CREATE TABLE T2
       (
        T2Id INT IDENTITY
                 PRIMARY KEY
                 NOT NULL
       ,T1Id INT NOT NULL
       ,OrderNum INT NOT NULL
       )
GO
CREATE TABLE T3
       (
        T3Id INT IDENTITY
                 PRIMARY KEY
                 NOT NULL
       ,T2Id INT NOT NULL
       ,ItemId INT NOT NULL
       ,Qty INT NOT NULL
       )
GO
CREATE TABLE Item
       (
        ItemId INT IDENTITY
                   PRIMARY KEY
                   NOT NULL
       ,Item VARCHAR(50)
       )
GO
ALTER TABLE dbo.T2
ADD CONSTRAINT FK_T2_T1 FOREIGN KEY( T1Id) REFERENCES dbo.T1 ( T1Id)
GO
ALTER TABLE dbo.T3
ADD CONSTRAINT FK_T3_T2 FOREIGN KEY( T2Id) REFERENCES dbo.T2 (T2Id)
GO
ALTER TABLE dbo.T3  
ADD CONSTRAINT FK_T3_Item FOREIGN KEY(ItemId) REFERENCES dbo.Item(ItemId)
GO
INSERT dbo.T1
        ( Customer )
SELECT 'FirstCust'
UNION
SELECT 'SecondCust'
UNION
SELECT 'ThirdCust' ;
INSERT dbo.T2
        ( T1Id, OrderNum )
SELECT 1, 1
UNION
SELECT 1, 2
UNION 
SELECT 2, 3
UNION
SELECT 3, 4
UNION
SELECT 3, 5 ;
INSERT dbo.Item
        ( Item )
SELECT 'Gunk'
UNION
SELECT 'Slop'
UNION
SELECT 'Glop'
UNION
SELECT 'Crud' ;
INSERT dbo.T3
        ( T2Id, ItemId, Qty )
SELECT 1,3,5
UNION
SELECT 1,2,2
UNION
SELECT 2,1,4
UNION
SELECT 3,3,10;

This creates tables that look like this:

Create a database with several tables with foreign key constraints

The data in the tables look like:

T1
Truncate all tables in a database
T2
Table2
T3
Table3
ItemTable
Unfortunately, in most complex database designs there are numerous foreign key relationships

If you attempt to truncate any of the tables you get this error:

BEGIN TRAN
TRUNCATE TABLE dbo.T2
ROLLBACK
If you attempt to truncate any of the tables you get this error:

If you attempt to delete data from the tables you get this error:

BEGIN TRAN
DELETE dbo.T1
ROLLBACK
If you attempt to delete data from the tables you get this error:

If you only want to use the DELETE statement against your tables, you can disable the constraints, delete your data and re-enable your constraints.

BEGIN TRAN
ALTER TABLE dbo.T1 NOCHECK CONSTRAINT ALL
ALTER TABLE dbo.T2 NOCHECK CONSTRAINT ALL
DELETE dbo.T1
SELECT * FROM dbo.T1 AS T
ALTER TABLE dbo.T1 CHECK CONSTRAINT ALL ALTER TABLE dbo.T2 CHECK CONSTRAINT ALL ROLLBACK
If you only want to use the DELETE statement against your tables, you can disable the constraints, delete your data and re-enable your constraints.

This works, but if you try do the same thing using TRUNCATE you get the following:

BEGIN TRAN
ALTER TABLE dbo.T1 NOCHECK CONSTRAINT ALL
ALTER TABLE dbo.T2 NOCHECK CONSTRAINT ALL
TRUNCATE TABLE  dbo.T1
SELECT * FROM dbo.T1 AS T
ROLLBACK
This works, but if you try do the same thing using TRUNCATE you get the following:

A solution that can TRUNCATE all tables

In order to truncate all tables in your database you must first remove all the foreign key constraints, truncate the tables, and then restore all the constraints.

The below script accomplishes this in an automated fashion, by executing the following steps:

  1. Create a table variable to store the constraint drop and creation scripts for the database
  2. Load the data for all tables in the database
  3. Execute a cursor to drop all constraints
  4. Truncate all tables
  5. Recreate all the constraints
 /* TRUNCATE ALL TABLES IN A DATABASE */
DECLARE @dropAndCreateConstraintsTable TABLE
        (
         DropStmt VARCHAR(MAX)
        ,CreateStmt VARCHAR(MAX)
        )
/* Gather information to drop and then recreate the current foreign key constraints  */
INSERT  @dropAndCreateConstraintsTable
        SELECT  DropStmt = 'ALTER TABLE [' + ForeignKeys.ForeignTableSchema
                + '].[' + ForeignKeys.ForeignTableName + '] DROP CONSTRAINT ['
                + ForeignKeys.ForeignKeyName + ']; '
               ,CreateStmt = 'ALTER TABLE [' + ForeignKeys.ForeignTableSchema
                + '].[' + ForeignKeys.ForeignTableName
                + '] WITH CHECK ADD CONSTRAINT [' + ForeignKeys.ForeignKeyName
                + '] FOREIGN KEY([' + ForeignKeys.ForeignTableColumn
                + ']) REFERENCES [' + SCHEMA_NAME(sys.objects.schema_id)
                + '].[' + sys.objects.[name] + ']([' + sys.columns.[name]
                + ']); '
        FROM    sys.objects
        INNER JOIN sys.columns
                ON ( sys.columns.[object_id] = sys.objects.[object_id] )
        INNER JOIN ( SELECT sys.foreign_keys.[name] AS ForeignKeyName
                           ,SCHEMA_NAME(sys.objects.schema_id) AS ForeignTableSchema
                           ,sys.objects.[name] AS ForeignTableName
                           ,sys.columns.[name] AS ForeignTableColumn
                           ,sys.foreign_keys.referenced_object_id AS referenced_object_id
                           ,sys.foreign_key_columns.referenced_column_id AS referenced_column_id
                     FROM   sys.foreign_keys
                     INNER JOIN sys.foreign_key_columns
                            ON ( sys.foreign_key_columns.constraint_object_id = sys.foreign_keys.[object_id] )
                     INNER JOIN sys.objects
                            ON ( sys.objects.[object_id] = sys.foreign_keys.parent_object_id )
                     INNER JOIN sys.columns
                            ON ( sys.columns.[object_id] = sys.objects.[object_id] )
                               AND ( sys.columns.column_id = sys.foreign_key_columns.parent_column_id )
                   ) ForeignKeys
                ON ( ForeignKeys.referenced_object_id = sys.objects.[object_id] )
                   AND ( ForeignKeys.referenced_column_id = sys.columns.column_id )
        WHERE   ( sys.objects.[type] = 'U' )
                AND ( sys.objects.[name] NOT IN ( 'sysdiagrams' ) )
/* SELECT * FROM @dropAndCreateConstraintsTable AS DACCT  --Test statement*/
DECLARE @DropStatement NVARCHAR(MAX)
DECLARE @RecreateStatement NVARCHAR(MAX)
/* Drop Constraints */
DECLARE Cur1 CURSOR READ_ONLY
FOR
        SELECT  DropStmt
        FROM    @dropAndCreateConstraintsTable
OPEN Cur1
FETCH NEXT FROM Cur1 INTO @DropStatement
WHILE @@FETCH_STATUS = 0
      BEGIN
            PRINT 'Executing ' + @DropStatement
            EXECUTE sp_executesql @DropStatement
            FETCH NEXT FROM Cur1 INTO @DropStatement
      END
CLOSE Cur1
DEALLOCATE Cur1
/* Truncate all tables in the database in the dbo schema */
DECLARE @DeleteTableStatement NVARCHAR(MAX)
DECLARE Cur2 CURSOR READ_ONLY
FOR
        SELECT  'TRUNCATE TABLE [dbo].[' + TABLE_NAME + ']'
        FROM    INFORMATION_SCHEMA.TABLES
        WHERE   TABLE_SCHEMA = 'dbo'
                AND TABLE_TYPE = 'BASE TABLE'
  /* Change your schema appropriately if you don't want to use dbo */
OPEN Cur2
FETCH NEXT FROM Cur2 INTO @DeleteTableStatement
WHILE @@FETCH_STATUS = 0
      BEGIN
            PRINT 'Executing ' + @DeleteTableStatement
            EXECUTE sp_executesql @DeleteTableStatement
            FETCH NEXT FROM Cur2 INTO @DeleteTableStatement
      END
CLOSE Cur2
DEALLOCATE Cur2
/* Recreate foreign key constraints  */
DECLARE Cur3 CURSOR READ_ONLY
FOR
        SELECT  CreateStmt
        FROM    @dropAndCreateConstraintsTable
OPEN Cur3
FETCH NEXT FROM Cur3 INTO @RecreateStatement
WHILE @@FETCH_STATUS = 0
      BEGIN
            PRINT 'Executing ' + @RecreateStatement
            EXECUTE sp_executesql @RecreateStatement
            FETCH NEXT FROM Cur3 INTO @RecreateStatement
      END
CLOSE Cur3
DEALLOCATE Cur3
GO   

When the above script is run against any database, all the tables are emptied and reset.

Next Steps


sql server categories

sql server webinars

subscribe to mssqltips

sql server tutorials

sql server white papers

next tip



About the author
MSSQLTips author Dave Bennett Dave Bennett is a Senior SQL Server DBA for a major telecommunications company. He has been working in technology for over 20 years.

This author pledges the content of this article is based on professional experience and not AI generated.

View all my tips



Comments For This Article




Tuesday, September 6, 2016 - 6:22:18 PM - Scott Back To Top (43271)

It's certainly possible to write a version of this script that handles multi-column foreign keys, and includes ON DELETE, ON UPDATE, and NOT FOR REPLICATION options.  It can be written to load all the commands into a VARCHAR(MAX) variable and execute them, but I would worry about what to do if an error occurs before all the foreign keys have been recreated.  Are all the ADD CONSTRAINT commands scripted out and saved somewhere?

My preference would be to have a script to generate all the commands, then copy them to a new SSMS window and run them manually.  That way if anything goes wrong you have a chance to fix the problem and continue running the commands until everything has been recreated.  You could add " IF OBJECT_ID('{foreign key name}') IS NULL " before the ALTER TABLE ADD commands so that section can be rerun.

If you're going to create a procedure that drops constraints and truncates data with no good way to roll back, then at least include a BACKUP DATABASE command at the beginning.

With all the warnings out of the way, here's how to script foreign key creation will multi-column keys and all options.  Note that no cursors are needed.

SET NOCOUNT ON;

SELECT  'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.' +  QUOTENAME(OBJECT_NAME(parent_object_id))
        + ' DROP CONSTRAINT ' + QUOTENAME(OBJECT_NAME(object_id)) + ';'
FROM sys.foreign_keys
WHERE is_ms_shipped = 0;

SELECT 'TRUNCATE TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(object_id)) + '.' + QUOTENAME(OBJECT_NAME(object_id)) + ';'
FROM sys.tables
WHERE is_ms_shipped = 0;

SELECT  'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.' +  QUOTENAME(OBJECT_NAME(parent_object_id))
        + ' ADD CONSTRAINT ' + QUOTENAME(OBJECT_NAME(object_id)) + ' FOREIGN KEY ('
        + STUFF(( SELECT ',' + QUOTENAME(name)
                  FROM sys.columns c
                  INNER JOIN sys.foreign_key_columns kc ON c.object_id = kc.parent_object_id AND c.column_id = kc.parent_column_id
                  WHERE kc.constraint_object_id = fk.object_id
                  ORDER BY kc.constraint_column_id
                  FOR XML PATH('') ), 1, 1, '')
        + ') REFERENCES (' + QUOTENAME(OBJECT_SCHEMA_NAME(fk.referenced_object_id)) + '.' + QUOTENAME(OBJECT_NAME(fk.referenced_object_id)) + ' ('
        + STUFF(( SELECT ',' + QUOTENAME(name)
                  FROM sys.columns c
                  INNER JOIN sys.foreign_key_columns kc ON c.object_id = kc.referenced_object_id AND c.column_id = kc.referenced_column_id
                  WHERE kc.constraint_object_id = fk.object_id
                  ORDER BY kc.constraint_column_id
                  FOR XML PATH('') ), 1, 1, '') + ')'
        + CASE fk.delete_referential_action WHEN 0 THEN '' ELSE ' ON DELETE ' + REPLACE(fk.delete_referential_action_desc, '_', ' ') END
        + CASE fk.update_referential_action WHEN 0 THEN '' ELSE ' ON UPDATE ' + REPLACE(fk.update_referential_action_desc, '_', ' ') END
        + CASE fk.is_not_for_replication WHEN 0 THEN '' ELSE ' NOT FOR REPLICATION' END + ';'
FROM sys.foreign_keys fk
WHERE fk.is_ms_shipped = 0;

 


Monday, September 14, 2015 - 7:16:58 AM - Linus Flüeler Back To Top (38672)

I dropped Daves script because of its drawbacks.

Here http://stackoverflow.com/questions/6028960/sql-truncate-database-how-to-truncate-all-tables I found a solution which works perfectly for me:

 /* TRUNCATE ALL TABLES IN A DATABASE */
EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'
EXEC sp_MSForEachTable 'DELETE FROM ?'
EXEC sp_MSForEachTable 'ALTER TABLE ? CHECK CONSTRAINT ALL'
EXEC sp_MSforeachtable 'DBCC CHECKIDENT ( ''?'', RESEED, 0)'
GO  

 

 


Monday, September 14, 2015 - 1:22:32 AM - vivek Back To Top (38668)

Thank you for the script it is useful....


Tuesday, September 1, 2015 - 7:07:09 AM - Linus Flüeler Back To Top (38578)

I used this code to clean the database before running an SSIS script for migrating data from a source DB.
But now I found a big problem in the code: It does not read correctly constraints with cascades: e.g. if your constraint has a
"ON DELETE CASCADE", it will not be re-created properly on the end of truncation. Your DB loses all cascaded deletion!
Probably defined indices will also disappear when using that script.

 

 

 

 


Thursday, August 20, 2015 - 12:39:42 PM - Dhamo Back To Top (38500)

 

How to use this code when the foreign key referrences are on multiple columns? I see currently it is generating drop & create scripts multiple times with same FK name but with each referencing columns.

 


Tuesday, March 3, 2015 - 12:49:45 PM - JeremyH Back To Top (36424)

When you enable a constraint after disabling it, you need to use "WITH CHECK CHECK CONSTRAINT" syntax in order for SQL Server to trust it again. If it is not trusted after enabling, the query engine can not use it to make better choices. This is a good blog on the topic - http://www.brentozar.com/blitz/foreign-key-trusted/.


Friday, December 26, 2014 - 5:49:32 PM - Jayant Back To Top (35769)

Thanks, your article made my day.


Friday, December 19, 2014 - 11:40:52 AM - JJ Back To Top (35686)

If i could have something like 100 tables in a db with complex schema i would not do it.


Friday, September 19, 2014 - 9:49:57 AM - Rukmini Back To Top (34626)

Thank you for the script it is useful


Saturday, September 13, 2014 - 7:02:41 AM - Yasir Back To Top (34519)

Really nice tip, saved my time. Thumbs Up @Dave.


Tuesday, July 1, 2014 - 3:32:02 AM - rajesh Back To Top (32486)

 

 

good work.


Thursday, May 1, 2014 - 10:44:15 AM - Scott Coleman Back To Top (30585)

I would probably use a temp table instead of a table variable to hold the commands.  That way if there is any unexpected error, the commands to recreate the foreign keys are still available.


Wednesday, April 30, 2014 - 11:51:25 AM - jonmcrawford Back To Top (30571)

Curious why you wouldn't just generate scripts to create structure without data? Already built into the tools?


Wednesday, April 30, 2014 - 4:50:49 AM - Leif Nielsen Back To Top (30556)

Thank you for the script.

 

I just have a small problem with it: it only seems to work on keys/references with a single column.


Tuesday, April 29, 2014 - 5:28:08 PM - Jeremy Kadlec Back To Top (30551)

Dave,

Congrats on your first tip!

Thank you,
Jeremy Kadlec
MSSQLTips.com Community Co-Leader


Tuesday, April 29, 2014 - 3:30:21 PM - Jean-Pierre Lebrasseur Back To Top (30548)

Thank you for the script it is usefull

In the my copy I add two fields in the script (Table name and reference Table name) that way you can extract only the one that you want to truncate and you have the reference to them.

 


Tuesday, April 29, 2014 - 12:43:24 PM - TimothyAWiseman Back To Top (30544)

This looks like an interesting and comprehensive solution.

 

Just out of curiosity, why use cursors as opposed to deleting the records from your table variable as you go and looping through the table variable?















get free sql tips
agree to terms