SQL DELETE Statement – Safe Syntax, Examples, and Best Practices | DbSchema
Table of Contents
- What the SQL DELETE statement does
- SQL DELETE syntax
- Delete a single row
- Delete multiple rows
- Delete with subqueries and joins
- Delete duplicate rows
- DELETE vs TRUNCATE vs DROP
- How to delete rows safely
- Run DELETE statements in DbSchema
- FAQ
- Conclusion
The SQL DELETE statement removes rows from a table. It does not remove the table itself, its columns, or its indexes. That sounds simple, but DELETE is one of the easiest commands to misuse because a weak WHERE clause can remove far more data than intended.
If you work carefully, DELETE is also one of the safest data-cleanup tools in SQL. The key is to preview the affected rows first, use transactions when the database supports them, and test your query in a controlled environment such as the DbSchema SQL Editor.
What the SQL DELETE statement does
Use DELETE when you want to:
- remove one specific row
- delete a batch of rows that match a condition
- clean duplicate records
- archive data first and then delete it
- remove child rows before changing a parent table
Use TRUNCATE or DROP TABLE only when your goal is different:
TRUNCATEremoves all rows from a table very quicklyDROP TABLEremoves the entire table definition
SQL DELETE syntax
The basic SQL DELETE syntax is:
DELETE FROM table_name
WHERE condition;
The WHERE clause is optional syntactically, but omitting it removes every row in the table:
DELETE FROM employees;
For that reason, many teams use a safety habit:
SELECT *
FROM employees
WHERE department = 'Support';
If the SELECT returns exactly the rows you expect, only then change it to:
DELETE FROM employees
WHERE department = 'Support';
Delete a single row
A single-row delete usually targets a primary key:
DELETE FROM students
WHERE student_id = 2;
This is the safest pattern because the condition is selective and easy to verify.
Example table
| student_id | first_name | last_name |
|---|---|---|
| 1 | John | Doe |
| 2 | Jane | Doe |
| 3 | Sam | Smith |
After running the delete, only rows 1 and 3 remain.
Delete multiple rows
To remove several rows, use a predicate that matches a group:
DELETE FROM students
WHERE last_name = 'Doe';
You can also use standard filtering operators:
DELETE FROM orders
WHERE order_date < DATE '2024-01-01';
DELETE FROM employees
WHERE salary BETWEEN 40000 AND 50000;
DELETE FROM tasks
WHERE status IN ('Cancelled', 'Archived');
For large cleanup jobs, avoid deleting millions of rows in one transaction if your database is sensitive to long locks or transaction log growth. Batch deletes are often safer:
DELETE FROM audit_log
WHERE created_at < DATE '2024-01-01'
LIMIT 5000;
LIMIT is not available in every database, so always check the syntax of your engine. SQL Server, for example, uses TOP, while PostgreSQL often uses a DELETE ... USING or CTE-based pattern for controlled batches.
Delete with subqueries and joins
Competitor tutorials that rank well almost always show how DELETE works beyond a single table.
Delete with a subquery
DELETE FROM orders
WHERE customer_id IN (
SELECT customer_id
FROM customers
WHERE customer_status = 'Inactive'
);
This is useful when the delete condition depends on values from another table.
Delete with a join
Join-based deletes are slightly different by database:
| Database | Common pattern |
|---|---|
| PostgreSQL | DELETE FROM target USING other_table ... |
| SQL Server | DELETE t FROM target t JOIN other_table ... |
| MySQL | DELETE t FROM target t JOIN other_table ... |
PostgreSQL example
DELETE FROM orders o
USING customers c
WHERE o.customer_id = c.customer_id
AND c.customer_status = 'Inactive';
SQL Server / MySQL style example
DELETE o
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE c.customer_status = 'Inactive';
If you are managing a PostgreSQL database, you can test these statements after connecting through the PostgreSQL JDBC driver. If you mainly work with MySQL or SQL Server, DbSchema also supports the MySQL JDBC driver and SQL Server JDBC driver.
Delete duplicate rows
Deleting duplicates is a common real-world use case and a frequent gap in weaker tutorials.
Here is a safe pattern with ROW_NUMBER():
WITH ranked_products AS (
SELECT
product_id,
product_name,
ROW_NUMBER() OVER (
PARTITION BY product_name
ORDER BY product_id
) AS row_num
FROM products
)
DELETE FROM ranked_products
WHERE row_num > 1;
The logic is:
- group duplicate rows by the natural duplicate key
- keep the first row
- delete rows ranked
2,3, and higher
Before running the delete, turn the CTE into a SELECT first so you can verify which rows are marked as duplicates.
DELETE vs TRUNCATE vs DROP
This comparison is one of the most searched follow-up questions for the SQL DELETE statement.
| Command | Removes | Can filter rows? | Usually logged row by row? | Keeps table structure? |
|---|---|---|---|---|
DELETE | Selected rows | Yes | Yes | Yes |
TRUNCATE | All rows | No | Usually less row-level logging | Yes |
DROP TABLE | Table and data | No | N/A | No |
Use this rule of thumb:
- choose
DELETEwhen you need control - choose
TRUNCATEwhen you want to empty a table fast - choose
DROP TABLEonly when the object itself should disappear
If you are deciding between DELETE and UPDATE, read SQL UPDATE Statement next. If your filter logic still feels unclear, review the SQL WHERE Clause first.
How to delete rows safely
Top-performing pages increasingly cover safety patterns, not just syntax.
1. Preview the rows
Always run the matching SELECT first:
SELECT *
FROM orders
WHERE status = 'Cancelled';
2. Use a transaction when possible
BEGIN;
DELETE FROM orders
WHERE status = 'Cancelled';
ROLLBACK;
If the result looks correct, replace ROLLBACK with COMMIT.
3. Watch foreign keys and cascades
If a parent row is referenced by child rows, the delete may:
- fail because of a foreign key constraint
- cascade automatically with
ON DELETE CASCADE - set child values to
NULLwithON DELETE SET NULL
If you need a refresher on relationships before deleting parent rows, see What Is a Foreign Key? or the PostgreSQL-specific guide to foreign keys in PostgreSQL.
4. Batch large deletes
Large deletes can create long transactions, lock contention, or heavy log usage. A batch strategy is usually safer for operational tables.
5. Keep an audit trail
Some databases support returning deleted rows directly. In SQL Server, for example, the OUTPUT clause is useful for auditing. In PostgreSQL, RETURNING can help capture deleted records before commit.
Run DELETE statements in DbSchema
DbSchema is useful for DELETE work because it helps you validate the impact before you run anything destructive:
- connect to the target database through the right driver
- open the SQL Editor and run the preview
SELECT - inspect related tables visually in the diagram to see foreign-key dependencies
- use the Relational Data Explorer to confirm which child rows might also be affected
- execute the delete only after the result set matches your intention
That is much safer than typing destructive SQL blindly into a console. DbSchema also makes it easy to keep cleanup scripts together with the schema model and documentation.
If your next step is moving rows into an archive table instead of deleting them immediately, see SQL INSERT INTO SELECT Statement.
FAQ
Can I use DELETE without WHERE?
Yes, but it deletes all rows in the table. That is one of the most common SQL mistakes.
Can I roll back a DELETE statement?
Yes, if your database and storage engine support transactions and you have not committed yet.
Does DELETE reset identity or auto-increment values?
Usually no. DELETE removes rows, but identity counters often keep their next value. TRUNCATE may reset them depending on the database.
Is DELETE slower than TRUNCATE?
Usually yes, because DELETE processes rows more individually and can fire triggers or enforce referential checks row by row.
How do I delete only duplicate rows?
Use a CTE or subquery with ROW_NUMBER() so you keep one copy and delete the rest safely.
Conclusion
The SQL DELETE statement is simple to learn and easy to get wrong. The best pattern is always the same: preview the target rows, understand related foreign keys, use transactions when possible, and batch large cleanup operations.
If you want to practice these examples with less risk, use DbSchema to connect through the right driver, preview the matching rows, and execute the final statement from the SQL editor only after you trust the result.