How to get the raw SQL for a Laravel delete/update/insert statement?

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company
# How to Get the Raw SQL for Laravel Delete/Update/Insert Statements: Beyond `toSql()` As a senior developer working within the Laravel ecosystem, we often seek maximum control over database operations. While Eloquent and the Query Builder provide elegant abstractions, there are specific scenarios—particularly those involving high-performance batch operations or complex asynchronous execution—where accessing the raw, parameterized SQL is essential. This blog post addresses the common challenge: how do we extract the underlying SQL for `DELETE`, `UPDATE`, or `INSERT` statements when the standard `toSql()` method proves insufficient? ## The Limitation of `toSql()` on Modification Statements We know that for `SELECT` queries, methods like `$model->where(...)->toSql()` work perfectly. They return the SQL string with appropriate placeholders (`?`) ready for binding. However, as demonstrated in many real-world scenarios, this functionality breaks down when applied to modification methods such as `delete()`, `update()`, or `insert()`. When you call `$model->where(...)->delete()`, the method executes the deletion and returns an integer representing the number of affected rows. It does not return a query builder object that holds the underlying SQL structure; it has already executed the operation. This leads to the frustrating error: "Call to a member function toSql() on integer." The desire is clear: we need the raw SQL string with its placeholders, but *without* actually executing the command, so we can reuse that statement within a prepared statement mechanism for performance gains, especially in multi-process or asynchronous environments. ## The Escape Hatch: Reaching the PDO Layer Since the Eloquent abstraction layer deliberately hides the execution details of modification methods to prioritize convenience and safety, obtaining the raw SQL requires stepping outside the standard Query Builder interface and interacting directly with the underlying database connection—specifically, the PDO object. This is often the necessary "escape hatch" when performance or architectural requirements dictate explicit control over statement preparation. To achieve true prepared statement reuse for `INSERT`, `UPDATE`, or `DELETE` operations, we must manually construct the SQL string using the Query Builder's logic and then use the raw PDO methods to prepare the statement outside of the standard Eloquent flow. Here is a practical demonstration of how this is achieved: ```php use Illuminate\Support\Facades\DB; // 1. Construct the base query using the Query Builder syntax $query = DB::table('users') ->where('id', '>', 1); // 2. Manually generate the SQL string from the builder (this part is often tricky for modifications) // For modification queries, we often need to build the structure manually if direct extraction fails. $sql = $query->toSql(); // Note: While this might fail on delete(), we rely on rebuilding the structure contextually. // 3. The robust approach involves understanding that for true reuse, you must define the SQL and bindings yourself // based on the builder's output or construct it explicitly. $sql = 'UPDATE `users` SET `status` = ? WHERE `id` = ?'; // Example hardcoded structure for clarity // 4. Prepare the statement using the raw PDO connection $statement = DB::connection()->getPdo()->prepare($sql); // 5. Execute in a loop or context where data is dynamic (e.g., batch updates) while (true) { $data = fetch_user_data(); // Simulate fetching data $statement->execute([$data['status'], $data['id']]); } ``` ## Performance vs. Abstraction: The Trade-off While the PDO method grants us the ability to reuse prepared statements, it forces us to step away from the clean, abstracted SQL grammar offered by Laravel's Query Builder. This introduces a significant trade-off: **abstraction versus performance and portability.** When you manually deal with quoting conventions (like using backticks `` for MySQL or double quotes `""` for PostgreSQL) and statement preparation, you introduce vendor lock-in concerns. If your application needs to migrate databases later, managing these low-level details becomes a maintenance burden. However, for high-throughput systems running custom artisan commands or asynchronous event loops—where the overhead of repeatedly parsing complex Eloquent methods is unacceptable—this manual approach provides the necessary granular control over statement execution. This level of optimization is crucial when dealing with heavy database interaction, especially as you scale your services built on Laravel. ## Conclusion In summary, while `toSql()` is a fantastic tool for read-only operations like `SELECT`, it hits a wall when dealing with the side effects of modification methods (`DELETE`, `UPDATE`). To achieve the goal of reusing prepared statements for performance optimization in complex applications, developers must be prepared to dive into the underlying PDO layer. This provides the necessary control, but it necessitates careful consideration of abstraction layers and database portability. For advanced use cases where raw speed is paramount, this manual approach remains the most powerful tool available.