Delete SQL Statement in SQL Server, Oracle and PostgreSQL

By:   |   Comments   |   Related: > SQL Server vs Oracle vs PostgreSQL Comparison


Problem

This is third article in the series regarding different behavior and syntax for SQL Server, Oracle and PostgreSQL. In this article we will look at the SQL DELETE statement.

Solution

As we have already seen with the Updates and Inserts SQL statements, the DELETE statement syntax can vary in the different SQL platforms as well as perform differently, but the DELETE statement is one of the core DBMS DML (Data Manipulation Language) statements.

For test purposes I will use the github freely downloadable database sample Chinook, as it is available in multiple RDBMS formats for download. It is a simulation of a digital media store, with some sample data, all you have to do is download the version you need and you have all the scripts for the data structure and the data.

SQL DELETE Basics

The basic syntax for the DELETE statement is quite simple and is the same for SQL Server, Oracle and PostgreSQL where we specify the DELETE command, name of the table and WHERE condition.

delete 
from Artist
where ArtistId=276;

It is strongly recommended to always have a WHERE clause for any delete operation, if you want to delete all table data it is better to use the TRUNCATE TABLE statement, because in all three RDBMS the TRUNCATE command is minimally logged (a row is not logged in the archive log or transaction log for every row deleted, but instead limited data is logged for the whole table) and thus is faster and uses less resources.

Something that I like to do before performing a DELETE on existing records is always execute a SELECT with the same query in order to check what the delete statement removes and be sure to check the result set before launching the command.

Another good deletion practice for SQL Server and PostgreSQL is to wrap the statement in an explicit transaction so that it will not be autocommited by default, but we will need to actually specify a COMMIT. That is not needed in Oracle as autocommit is not the default and you are always in an explicit transaction, thus you can ROLLBACK or COMMIT. This behavior can be tricky for DBA and developers coming from other RDBMS. I remember that once I created some locks in a table because I didn't commit a massive UPDATE! The same applies for people coming from Oracle and expecting to have the possibility to ROLLBACK is even more dangerous! We will see examples of this in the remaining examples.

DELETE Data Using a Join and an Explicit Transaction

Another possibility to limit and choose the rows to delete is joining (INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL JOIN, multiple-table JOINs) with other tables. For example, let's imagine that we would like to delete rows that were inserted in the INSERT tip in the table Discount_table, based on customers from Austria that have bought Rock and Metal genres.

SQL Server

BEGIN TRANSACTION
 
DELETE
FROM dbo.Discount_Table
INNER JOIN dbo.Invoice ON Invoice.CustomerId = Discount_Table.Customerid
INNER JOIN dbo.InvoiceLine ON InvoiceLine.InvoiceId = Invoice.InvoiceId
INNER JOIN dbo.Customer ON Customer.CustomerId = Discount_Table.Customerid
INNER JOIN dbo.Track ON Track.TrackId = InvoiceLine.TrackId
WHERE dbo.Customer.Country='Austria' AND GenreId IN (1,3)

Here's the result of our DELETE:

query results

And since we are happy that it is only one row as expected we can now COMMIT:

commit
query results

Oracle

Now let's see the way in which the same thing can be achieved in Oracle, as with the UPDATE with joins of my previous tip, on Oracle we have to do a different thing as a DELETE with joins is not allowed:

DELETE
from DISCOUNT_TABLE
where customerid in 
(select discount_table.customerid FROM Discount_Table
INNER JOIN Invoice ON Invoice.CustomerId = Discount_Table.Customerid
INNER JOIN InvoiceLine ON InvoiceLine.InvoiceId = Invoice.InvoiceId
INNER JOIN Customer ON Customer.CustomerId = Discount_Table.Customerid
INNER JOIN Track
ON Track.TrackId = InvoiceLine.TrackId
WHERE Customer.Country='Austria' AND GenreId IN (1,3));
query results

And then commit it:

commit;

As you can see, we cannot use the same syntax nor the same style with joins, if we try to do something like this:

DELETE
from
(select discount_table.* FROM Discount_Table
INNER JOIN Invoice ON Invoice.CustomerId = Discount_Table.Customerid
INNER JOIN InvoiceLine ON InvoiceLine.InvoiceId = Invoice.InvoiceId
INNER JOIN Customer ON Customer.CustomerId = Discount_Table.Customerid
INNER JOIN Track ON Track.TrackId = InvoiceLine.TrackId
WHERE Customer.Country='Austria' AND GenreId IN (1,3));

We end up having an error that is quite self-explanatory as Oracle tries to protect from deleting more rows that it cannot clearly identify:

Error at Command Line : 3 Column : 1
Error report -
SQL Error: ORA-01752: cannot delete from view without exactly one key-preserved table
01752. 00000 -  "cannot delete from view without exactly one key-preserved table"
*Cause:    The deleted table had
           - no key-preserved tables,
           - more than one key-preserved table, or
           - the key-preserved table was an unmerged view.
*Action:   Redefine the view or delete it from the underlying base tables.

So, as we've seen in the example above, we must do a different things in order to get only the number of rows that we want to delete. By the way this is the standard ANSI SQL behavior as instead of joins we should use a subquery or an IN clause in order to identify the rows to be deleted. Obviously, this same style of using a subquery, as it is standard ANSI SQL, is also possible in SQL Server.

PostgreSQL

Now let's see the behavior of PostgreSQL. Here we have basically two ways as in SQL Server, one is to use the USING clause which again is not standard and is basically a JOIN as in SQL Server, the other is to use a subquery:

begin;
 
DELETE
from "discount_table"
where "customerid" in 
(select "discount_table"."customerid" FROM "discount_table"
INNER JOIN "Invoice" ON "Invoice"."CustomerId" = "discount_table"."customerid"
INNER JOIN "InvoiceLine" ON "InvoiceLine"."InvoiceId" = "Invoice"."InvoiceId"
INNER JOIN "Customer" ON "Customer"."CustomerId" = "discount_table"."customerid"
INNER JOIN "Track" ON "Track"."TrackId" = "InvoiceLine"."TrackId"
WHERE "Customer"."Country"='Austria' AND "GenreId" IN (1,3));
query results

Since we'd like to demonstrate other possible syntax, we will rollback this transaction:

rollback;
query results

Delete Data using a CTE

Let's now introduce something that is possible in all three RDBMS, the use of a CTE instead of a subquery.

PostegreSQL

begin;
with rows_to_delete as
(select distinct "discount_table".* FROM "discount_table"
INNER JOIN "Invoice" ON "Invoice"."CustomerId" = "discount_table"."customerid"
INNER JOIN "InvoiceLine" ON "InvoiceLine"."InvoiceId" = "Invoice"."InvoiceId"
INNER JOIN "Customer" ON "Customer"."CustomerId" = "discount_table"."customerid"
INNER JOIN "Track" ON "Track"."TrackId" = "InvoiceLine"."TrackId"
WHERE "Customer"."Country"='Austria' AND "GenreId" IN (1,3))
DELETE
from "discount_table"
using rows_to_delete
where "discount_table"."customerid"=rows_to_delete."customerid";
query results

As you can see, we have joined the CTE with the USING clause which as I said before is not ANSI SQL standard.

Now we can commit this delete:

commit;
query results

SQL Server

We've just seen in the example before that a CTE can be used in order to filter the rows to be deleted in PostgreSQL, now we'll see the syntax in SQL Server, which is pretty much the same with the SELECT statement and WHERE condition, so using the same example (and having re-inserted the deleted row in the discount_table):

begin tran
;with rows_to_delete as
(select distinct discount_table.* FROM discount_table
INNER JOIN Invoice ON Invoice.CustomerId = discount_table.customerid
INNER JOIN InvoiceLine ON InvoiceLine.InvoiceId = Invoice.InvoiceId
INNER JOIN Customer ON Customer.CustomerId = discount_table.customerid
INNER JOIN Track ON Track.TrackId = InvoiceLine.TrackId
WHERE Customer.Country='Austria' AND GenreId IN (1,3))
DELETE
from discount_table
from Discount_Table
inner join rows_to_delete
on discount_table.customerid=rows_to_delete.customerid
query results

We can now rollback the transaction as I'd like to use this row again for the next example:

rollback;
query results

Oracle

In Oracle the syntax is a little bit different as the CTE must not be declared first, but after the DELETE statement as in this example:

DELETE
from chinook.discount_table
where customerid in  
(with rows_to_delete as
(select distinct chinook.discount_table.* FROM chinook.discount_table
INNER JOIN chinook.Invoice ON chinook.Invoice.CustomerId = chinook.discount_table.customerid
INNER JOIN chinook.InvoiceLine ON chinook.InvoiceLine.InvoiceId = chinook.Invoice.InvoiceId
INNER JOIN chinook.Customer ON chinook.Customer.CustomerId = chinook.discount_table.customerid
INNER JOIN chinook.Track ON chinook.Track.TrackId = chinook.InvoiceLine.TrackId
WHERE chinook.Customer.Country='Austria' AND GenreId IN (1,3))
select customerid from rows_to_delete);
query results

And we can commit it:

commit;
query results

Return Output from Deleted Rows

Let's see now another important feature common on all three RDBMS, the possibility of returning output for deleted rows, exactly as I've already shown on my previous INSERT tip.

SQL Server

begin tran
;with rows_to_delete as
(select distinct discount_table.* FROM discount_table
INNER JOIN Invoice ON Invoice.CustomerId = discount_table.customerid
INNER JOIN InvoiceLine ON InvoiceLine.InvoiceId = Invoice.InvoiceId
INNER JOIN Customer ON Customer.CustomerId = discount_table.customerid
INNER JOIN Track ON Track.TrackId = InvoiceLine.TrackId
WHERE Customer.Country='Austria' AND GenreId IN (1,3))
DELETE
from discount_table
output deleted.*
from Discount_Table
inner join rows_to_delete
on discount_table.customerid=rows_to_delete.customerid
query results

In this way we can output the row that has been deleted, we can also put this result into table. Below we will use a temp table to store the data.

begin tran
 
create table #temp_deleted
(
   [Customerid] [int] NULL,
   [Genretotal] [numeric](10, 2) NULL,
   [DiscountPercentage] [tinyint] NULL
) 
 
;with rows_to_delete as
(select distinct discount_table.* FROM discount_table
INNER JOIN Invoice ON Invoice.CustomerId = discount_table.customerid
INNER JOIN InvoiceLine ON InvoiceLine.InvoiceId = Invoice.InvoiceId
INNER JOIN Customer ON Customer.CustomerId = discount_table.customerid
INNER JOIN Track ON Track.TrackId = InvoiceLine.TrackId
WHERE Customer.Country='Austria' AND GenreId IN (1,3))
DELETE
from discount_table
output deleted.*
into #temp_deleted
from Discount_Table
inner join rows_to_delete
on discount_table.customerid=rows_to_delete.customerid
query results

Let's check the temp table:

SELECT*
FROM #temp_deleted
query results

Now we can finally commit:

commit
query results

Oracle

Let's take a look at the Oracle syntax, as with INSERT statement the keyword here is RETURNING instead of OUTPUT:

SET SERVEROUTPUT ON
 
DECLARE
   my_CustomerId chinook.discount_table.customerid%TYPE;
   my_Genretotal chinook.discount_table.genretotal%TYPE;
   my_discountpercentage chinook.discount_table.discountpercentage%TYPE;
BEGIN
 
DELETE
from chinook.discount_table
where customerid in  
(select discount_table.customerid FROM Discount_Table
INNER JOIN Invoice ON Invoice.CustomerId = Discount_Table.Customerid
INNER JOIN InvoiceLine ON InvoiceLine.InvoiceId = Invoice.InvoiceId
INNER JOIN Customer ON Customer.CustomerId = Discount_Table.Customerid
INNER JOIN Track ON Track.TrackId = InvoiceLine.TrackId
WHERE Customer.Country='Austria' AND GenreId IN (1,3))
returning customerid, genretotal,discountpercentage into my_CustomerId,my_Genretotal, my_discountpercentage;
dbms_output.put_line(my_CustomerId ||' '||my_Genretotal||' '||my_discountpercentage);
end;
query results

Please note the SET SERVEROUTPUT ON instruction is used to display the results with dbms_output.put_line and the way I declared the “my” variables to store the DELETE output, using the same type as the table. This can be useful in many situations in Oracle and I used this same style on my previous INSERT tip. Now we can finally commit and delete this row:

commit;
query results

PostegreSQL

Now let's take a look at the PostgreSQL syntax, again we see the use of RETURNING and we have the same syntax used in the INSERT tip:

begin;
 
DELETE
from "discount_table"
where "customerid" in 
(select "discount_table"."customerid" FROM "discount_table"
INNER JOIN "Invoice" ON "Invoice"."CustomerId" = "discount_table"."customerid"
INNER JOIN "InvoiceLine" ON "InvoiceLine"."InvoiceId" = "Invoice"."InvoiceId"
INNER JOIN "Customer" ON "Customer"."CustomerId" = "discount_table"."customerid"
INNER JOIN "Track" ON "Track"."TrackId" = "InvoiceLine"."TrackId"
WHERE "Customer"."Country"='Austria' AND "GenreId" IN (1,3))
returning "discount_table"."customerid", "genretotal","discountpercentage" ;
query results

And we can finally commit in PostgreSQL:

commit;
query results

Summary

We have seen in this tip the various syntaxes and possibilities related to the statement DELETE in three DBMS, this is part 3 of a series, please find the other links below.

Next Steps
  • Link to Update with joins in the three different RDBMS
  • Link to INSERT in the three different RDBMS
  • As mentioned at the beginning, there are also performance differences for the DELETE statement, in particular it seems that PostgreSQL is a lot faster, but there's a reason for this as you can see in the comments section of this tip: SQL Server vs MySQL vs PostgreSQL Delete Performance Comparison. In fact records are not directly deleted in PostgreSQL, but only marked as deleted. The explicit vacuum is the instruction that in PostgreSQL clears the dead tuples and releases the space to the table, until that moment the space occupied by the deleted tuples cannot be reused, so this is a big difference from the other RDBMS. Note that the vacuum can be automated using autovacuum, otherwise it can be invoked manually even for a single table. Here is the official documentation of autovacuum.
  • Another interesting feature in Oracle is the possibility of using the CASCADE keyword in order to delete rows referenced as Foreign Keys in other tables, so that the DELETE will remove in just one step rows from the main table and also the rows referenced. You can see an example in this tutorial at the point D. This can also be done in SQL Server as shown here.


sql server categories

sql server webinars

subscribe to mssqltips

sql server tutorials

sql server white papers

next tip



About the author
MSSQLTips author Andrea Gnemmi Andrea Gnemmi is a Database and Data Warehouse professional with almost 20 years of experience, having started his career in Database Administration with SQL Server 2000.

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

















get free sql tips
agree to terms