Running a SQL delete query in Laravel

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company
# 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