Running a SQL delete query in Laravel
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
# Running Complex SQL Deletes in Laravel: Mastering Raw Queries for Data Cleanup
As senior developers working with the Laravel ecosystem, we frequently encounter scenarios where standard Eloquent methods fall short. When dealing with complex data manipulations—such as finding and deleting duplicate records based on group logic—we often need to drop down to raw SQL. This is where understanding how Laravel's Database facade bridges the gap between application logic and the database execution becomes critical.
The challenge you face involves a classic data hygiene problem: identifying duplicate rows based on a specific attribute (`norad_cat_id`) and selectively deleting them while retaining only the record with the highest primary key (`id`). Let's break down how to approach this using Laravel, focusing on the raw SQL execution methods.
## The Eloquent vs. Raw SQL Dilemma
Laravel strongly encourages the use of Eloquent ORM and the Query Builder for most database interactions. This provides type safety, abstraction, and excellent developer experience. However, for highly complex, set-based operations like the one you described—involving self-joins and derived tables to identify duplicates before deletion—raw SQL often proves to be the most direct and performant solution.
As noted in Laravel documentation regarding running queries (`https://laravel.com/docs/5.4/database#running-queries`), you have options like `DB::delete()`, `DB::update()`, and `DB::statement()`. The choice depends on what you want the database driver to return:
* `DB::select()` or `DB::raw()`: For retrieving data.
* `DB::insert()`: For inserting new records.
* `DB::delete()` or `DB::update()`: For executing DML operations.
* `DB::statement()`: For executing arbitrary SQL statements that do not return a result set (like complex `DELETE` statements).
## Deleting Duplicates with Optimized SQL Logic
Your proposed raw SQL structure is actually quite sophisticated and demonstrates the correct logical approach for this task:
```sql
delete satellites
from satellites
inner join (
select max(id) as lastId, norad_cat_id
from satellites
group by norad_cat_id
having count(*) > 1) duplic on duplic.norad_cat_id = satellites.norad_cat_id
where satellites.norad_cat_id < duplic.lastId;
```
This query effectively identifies all rows that share a `norad_cat_id` but are *not* the one possessing the maximum `id` for that group, and then deletes them. This logic