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.

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.





9 Comments. Leave new
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.
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.
That seems like one of those esoteric language things that no one uses, like numbered procs. proc;1 procl;2
Nice trick. Working on SQL for more than 3 year and not knowing this.
Thanks.
Your are right Pinal we dont know this option. Thank you
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
/*
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
good day ,sir g,
this query is not working on oracle 11g server,please tell me what should we do in this problem
It is not for Oracle, it is for SQL Server.