Drop Multiple Tables with One DROP TABLE Statement

You can drop multiple tables with one DROP TABLE statement by listing the table names, separated by commas. The statement is shorter than three separate ones. It has one rule about order, and one surprise about errors.

Gouache painting of a vermilion cart carrying three small stacked wooden tables

One Statement, Several Tables

Many developers write one DROP TABLE for every table. You can drop multiple tables in one statement, because the syntax accepts a list. Name each table, put a comma between the names, and SQL Server removes them in the order you wrote them. The demo creates three staging tables first, then removes all of them with a single statement.

IF DB_ID(N'DropTablesDemo') IS NULL CREATE DATABASE DropTablesDemo;
GO
USE DropTablesDemo;
GO
DROP TABLE IF EXISTS dbo.StageOrders, dbo.StageCustomers, dbo.StageProducts;
CREATE TABLE dbo.StageOrders (ID int NOT NULL PRIMARY KEY, Item nvarchar(40) NOT NULL);
CREATE TABLE dbo.StageCustomers (ID int NOT NULL PRIMARY KEY, FullName nvarchar(60) NOT NULL);
CREATE TABLE dbo.StageProducts (ID int NOT NULL PRIMARY KEY, Title nvarchar(60) NOT NULL);
SELECT name FROM sys.tables ORDER BY name;
name
StageCustomers
StageOrders
StageProducts
DROP TABLE dbo.StageOrders, dbo.StageCustomers, dbo.StageProducts;
SELECT COUNT(*) AS TablesLeft FROM sys.tables;
TablesLeft
0

All three tables are gone. The list can hold as many tables as you need, and the tables can belong to different schemas. Cleanup scripts that create several staging tables benefit most from this form. The cleanup becomes one line at the end of the script.

Put the same line at the top of a script, with IF EXISTS, and the script can run twice. It no longer fails on the tables of the last run.

Foreign Keys Decide the Order

The order of the list matters when the tables are linked. A table that holds a foreign key must come before the table it points to. SQL Server can’t remove a parent while a child still references it. The next script builds a small parent and child, plus an unrelated table.

CREATE TABLE dbo.Customers (CustomerID int NOT NULL PRIMARY KEY);
CREATE TABLE dbo.Orders (
    OrderID    int NOT NULL PRIMARY KEY,
    CustomerID int NOT NULL CONSTRAINT FK_Orders_Customers REFERENCES dbo.Customers (CustomerID)
);
CREATE TABLE dbo.Notes (NoteID int NOT NULL PRIMARY KEY);

Now list the parent first. This is the wrong order.

DROP TABLE dbo.Customers, dbo.Orders, dbo.Notes;
Msg 3726, Level 16, State 1, Line 1
Could not drop object 'dbo.Customers' because it is referenced by a FOREIGN KEY constraint.
SELECT name FROM sys.tables ORDER BY name;
name
Customers

The error names Customers, and the table is still there. The result lists only Customers, so Orders and Notes are gone. Without TRY and CATCH, SQL Server reports the error for the failing table. It still drops the rest of the list. A multi-table drop is not all or nothing, so check the result after any error.

The right order lists Orders before Customers. The IF EXISTS form, available in SQL Server 2016 and later, adds a second benefit. It skips tables that don’t exist, so the same line works on a database that never created some of them. The permission rule is the same as for a single drop. You need ALTER permission on the schema, or CONTROL permission on each table.

DROP TABLE IF EXISTS dbo.Orders, dbo.Customers, dbo.Notes;
SELECT COUNT(*) AS TablesLeft FROM sys.tables;
TablesLeft
0

Inside a TRY Block

A TRY block changes the behavior. The next script creates the parent and child again and drops the parent first inside TRY. The CATCH block returns the error number, and the last query lists the tables that remain.

CREATE TABLE dbo.Customers (CustomerID int NOT NULL PRIMARY KEY);
CREATE TABLE dbo.Orders (
    OrderID    int NOT NULL PRIMARY KEY,
    CustomerID int NOT NULL CONSTRAINT FK_Orders_Customers REFERENCES dbo.Customers (CustomerID)
);
GO
BEGIN TRY
    DROP TABLE dbo.Customers, dbo.Orders;
END TRY
BEGIN CATCH
    SELECT ERROR_NUMBER() AS ErrorNumber;
END CATCH;
SELECT name FROM sys.tables ORDER BY name;
GO
DROP TABLE IF EXISTS dbo.Orders, dbo.Customers;
ErrorNumber
3726
name
Customers
Orders

The first error stops the statement. Nothing after it is dropped, so both tables remain. The two behaviors differ, and both are easy to forget. They are a good reason to list the tables in the right order every time.

Drop Tables by Name Pattern

Some scripts create work tables with a shared prefix, such as Tmp_. They need to remove all of those tables at the end. Read the names from the catalog and build one DROP TABLE statement. The script below creates three prefixed tables and one table to keep. It then builds the statement with STRING_AGG, shows it, and runs it.

CREATE TABLE dbo.Tmp_LoadA (ID int);
CREATE TABLE dbo.Tmp_LoadB (ID int);
CREATE TABLE dbo.Tmp_Totals (ID int);
CREATE TABLE dbo.KeepMe (ID int);
GO
DECLARE @sql nvarchar(max) = (
    SELECT N'DROP TABLE IF EXISTS '
         + STRING_AGG(QUOTENAME(SCHEMA_NAME(schema_id)) + N'.' + QUOTENAME(name), N', ')
         + N';'
    FROM sys.tables
    WHERE name LIKE N'Tmp[_]%');
SELECT @sql AS GeneratedStatement;
EXEC (@sql);
SELECT name FROM sys.tables ORDER BY name;
GeneratedStatement
DROP TABLE IF EXISTS [dbo].[Tmp_LoadA], [dbo].[Tmp_LoadB], [dbo].[Tmp_Totals];
name
KeepMe

QUOTENAME wraps each name in brackets, so odd names can’t break the statement. The LIKE pattern uses a bracket for the underscore, because an underscore alone matches any character. STRING_AGG needs SQL Server 2017 or later. Always print the generated statement before you run it on a real database.

Temporary Tables Work the Same Way

A list also works for temporary tables. The next script creates two and drops both in one statement. The check shows that none is left.

CREATE TABLE #ScratchOne (ID int);
CREATE TABLE #ScratchTwo (ID int);
DROP TABLE #ScratchOne, #ScratchTwo;
SELECT COUNT(*) AS ScratchLeft FROM tempdb.sys.tables WHERE name LIKE N'#Scratch%';
ScratchLeft
0

You could argue that three separate statements are easier to read, and that a failing one is easier to find. That holds for a long script of different jobs. For a block of scratch tables that belong together, one line says what it means. You also run it once.

What to Remember

To drop multiple tables, list the referencing table before the table it references. Add IF EXISTS when a table can be missing. Check the table list after an error, because the rest of the list is still dropped. Generate the statement from the catalog when the tables share a name pattern, and print it first.

When you finish with the demo, run the cleanup script.

USE master;
GO
IF DB_ID(N'DropTablesDemo') IS NOT NULL
BEGIN
    ALTER DATABASE DropTablesDemo SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE DropTablesDemo;
END;

A DROP statement is not a list of chores, it is a list of consequences.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

SQL Constraint and Keys, SQL Scripts, SQL Table Operation
Previous Post
Full-Text Index Blocking Redo on an Always On Secondary
Next Post
SQL SERVER – How to Get List of SQL Server Instances Installed on a Machine Via T-SQL?

Related Posts

9 Comments. Leave new

  • Tim Cartwright
    October 24, 2018 2:38 am

    Wow, I did not know you can do that. Does it drop them in any particular order? The order listed maybe? Just thinking of FKs.

    Reply
    • As per documentation – Multiple tables can be dropped in any database. If a table being dropped references the primary key of another table that is also being dropped, the referencing table with the foreign key must be listed before the table holding the primary key that is being referenced.

      Reply
      • Tim Cartwright
        October 24, 2018 8:04 pm

        That seems like one of those esoteric language things that no one uses, like numbered procs. proc;1 procl;2

  • Shantilal Suthar
    October 24, 2018 12:02 pm

    Nice trick. Working on SQL for more than 3 year and not knowing this.

    Thanks.

    Reply
  • Your are right Pinal we dont know this option. Thank you

    Reply
  • halwer@gmx.de
    April 10, 2019 1:54 pm

    That’s cool. IF EXITS is also supported. Nice Feature.

    create table temp1(col1 NVARCHAR(1))
    –create table temp2(col1 NVARCHAR(1))
    create table temp3(col1 NVARCHAR(1))

    DROP TABLE IF EXISTS temp1,temp2,temp3

    Reply
  • Ahliana Byrd
    June 19, 2019 1:20 am

    /*
    I create multiple temporary stored procedures and temporary tables (NOT temp tables, I work in Azure).
    I’m essentially creating programming pieces for my overall stored procedure to use, such as logging or other
    specialized pieces. I prefix them all with _TEMP_, which makes them easy to search and destroy, or identify
    if they don’t get cleaned up.

    The following cursors clean them up.
    */

    GO
    DECLARE @Name NVARCHAR(MAX)
    DECLARE @SQL NVARCHAR(MAX)

    DECLARE curProcedures CURSOR FOR
    SELECT NAME
    FROM SYS.OBJECTS
    WHERE NAME LIKE ‘_TEMP_%’
    AND TYPE = ‘P’

    OPEN curProcedures
    FETCH NEXT FROM curProcedures INTO @Name
    WHILE @@FETCH_STATUS = 0
    BEGIN
    SET @SQL = ‘DROP PROCEDURE dbo.’ + QUOTENAME(@Name)
    EXEC sp_executesql @SQL
    FETCH NEXT FROM curProcedures INTO @Name
    END
    CLOSE curProcedures
    DEALLOCATE curProcedures
    GO

    GO
    DECLARE @Name NVARCHAR(MAX)
    DECLARE @SQL NVARCHAR(MAX)

    DECLARE curTables CURSOR FOR
    SELECT NAME
    FROM SYS.OBJECTS
    WHERE NAME LIKE ‘_TEMP_%’
    AND TYPE = ‘u’

    OPEN curTables
    FETCH NEXT FROM curTables INTO @Name
    WHILE @@FETCH_STATUS = 0
    BEGIN
    SET @SQL = ‘DROP TABLE dbo.’ + QUOTENAME(@Name)
    EXEC sp_executesql @SQL
    FETCH NEXT FROM curTables INTO @Name
    END
    CLOSE curTables
    DEALLOCATE curTables
    GO

    Reply
  • good day ,sir g,
    this query is not working on oracle 11g server,please tell me what should we do in this problem

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.