How to Capture All Rows Deleted by a DELETE Statement in SQL Server

Any time you prepare to delete data from a database, you will probably wonder whether or not to log the deleted data somewhere… just in case.

One way to do this is to construct a SELECT statement to identify the rows you want to delete, and then use it to copy those rows into a separate log table, before doing the actual delete. If you’re using SQL Server, another option is to use the OUTPUT clause. This article discusses the later.

The OUTPUT clause allows us to capture the data affected by INSERT, UPDATE, DELETE, and MERGE operations.

We can use this clause either to return the affected rows to the user, save them to a table variable, or log them into another table.

Let’s go ahead and run some examples that demonstrate how we can use the OUTPUT clause to view and log deleted rows in SQL Server.

Setup: Create the Table and Insert Data

First, let’s create a table and insert some data to delete:

CREATE TABLE Orders (
    OrderID INT PRIMARY KEY,
    CustomerID INT,
    OrderDate DATE
);

INSERT INTO Orders (OrderID, CustomerID, OrderDate)
VALUES (1, 101, '2024-01-15'),
       (2, 102, '2024-02-10'),
       (3, 103, '2024-03-05'),
       (4, 104, '2024-03-05'),
       (5, 105, '2024-03-05');

Now let’s go through and delete some of those records.

Example 1: Return Deleted Rows to the User

This example shows how to use the OUTPUT clause to return the deleted rows directly to the user:

DELETE FROM Orders
OUTPUT 
    DELETED.OrderID, 
    DELETED.CustomerID
WHERE OrderID = 2;

Output:

OrderID  CustomerID
------- ----------
2 102

This query deleted the row where OrderID = 2 and returned the deleted OrderID and CustomerID.

Here, I decided not to return all columns – I omitted the OrderDate column. We can choose whichever columns we want, as long as we prefix it with DELETED.

The DELETED prefix specifies the value deleted by the update or delete operation, and any existing values that don’t change with the current operation. It reflects the value before the DELETE operation is completed.

Example 2: Save the Result to a Variable

We can save the deleted rows to a table variable for further processing if required. To do this, we need to use the INTO clause of the OUTPUT clause:

-- Create a table variable to store output
DECLARE @DeletedOrders TABLE (
    OrderID INT, 
    CustomerID INT, 
    OrderDate DATE
    );

-- Delete data and capture the deleted values
DELETE FROM Orders
OUTPUT 
    DELETED.OrderID, 
    DELETED.CustomerID, 
    DELETED.OrderDate
    INTO @DeletedOrders
WHERE OrderID = 3;

-- Display the deleted rows
SELECT * FROM @DeletedOrders;

Output:

OrderID  CustomerID  OrderDate               
------- ---------- ------------------------
3 103 2024-03-05T00:00:00.000Z

I returned the result in a SELECT statement, but we could also use the variable to do whatever we want.

In this case I decided to include the OrderDate column.

Example 3: Log Deleted Rows into a Table

One obvious use case for the deleted data is to log it to a permanent (or temporary) table. We can use the OUTPUT INTO clause to do this:

Setup: Creating the Log Table

Let’s create a log table to store the deleted rows:

CREATE TABLE DeletedOrdersLog (
    OrderID INT,
    CustomerID INT,
    DeletedOn DATETIME DEFAULT GETDATE()
);

DELETE with OUTPUT INTO Clause

Now we can go ahead with deleting stuff and logging it in our log table:

-- Delete the row/s
DELETE FROM Orders
OUTPUT 
    DELETED.OrderID, 
    DELETED.CustomerID, 
    GETDATE() 
    INTO DeletedOrdersLog (
        OrderID, 
        CustomerID, 
        DeletedOn
        )
WHERE OrderID = 4;

-- Return the deleted row/s from the log table
SELECT * FROM DeletedOrdersLog;

Output:

OrderID  CustomerID  DeletedOn               
------- ---------- ------------------------
4 104 2024-09-20T01:58:18.460Z

This query deletes the row where OrderID = 4 and logs the OrderID, CustomerID, and the current timestamp into the DeletedOrdersLog table.