How to Run Raw SQL Queries in Laravel with DB::raw (2026 Guide)

How to Run Raw SQL Queries in Laravel with DB::raw (2026 Guide)

If you are building real Laravel apps, there will be moments when Eloquent alone is not enough. That is where DB::raw and raw query helpers come in. In this guide, you will learn exactly when to use Laravel db raw patterns in Laravel 13 with MySQL 8, how to keep them safe, and how to avoid performance mistakes.

This is part of a broader Laravel project lifecycle series. If you are just getting started, read the upcoming guides on creating a Laravel project, managing .env safely, and deployment patterns.

What is DB::raw in Laravel?

DB::raw lets you pass a raw SQL expression into the query builder when Laravel does not provide a clean fluent method for what you need.

Use it when you need:

  • SQL expressions like CASE, COALESCE, or arithmetic operations.
  • Database-specific functions in MySQL 8.
  • Advanced ordering and reporting queries.

Avoid it when a regular query builder or Eloquent method can express the same query clearly.

DB::raw vs selectRaw vs whereRaw vs Eloquent

Quick rule of thumb:

Use caseBest option
Standard CRUD and relationshipsEloquent
Normal filters and joinsQuery Builder
Computed columns (CASE, SUM(price * qty))selectRaw or DB::raw
Custom SQL predicateswhereRaw / havingRaw with bindings
Complex reports with groupingselectRaw + groupByRaw

In practice, prefer selectRaw, whereRaw, and friends over directly scattering DB::raw everywhere. They are easier to read and support parameter bindings in a predictable way.

Example 1: Aggregate totals with aliases

You need monthly revenue and order count for a dashboard.

use Illuminate\Support\Facades\DB;

$stats = DB::table('orders')
    ->selectRaw('DATE_FORMAT(created_at, "%Y-%m") as month')
    ->selectRaw('COUNT(*) as orders_count')
    ->selectRaw('SUM(total_amount) as revenue_total')
    ->where('status', 'paid')
    ->groupByRaw('DATE_FORMAT(created_at, "%Y-%m")')
    ->orderBy('month')
    ->get();

Why raw here? MySQL date formatting for grouped reporting is easier in SQL than in PHP post-processing.

Example 2: Computed status column with CASE

You want a label derived from multiple columns.

$users = DB::table('users')
    ->select('id', 'name', 'last_login_at')
    ->selectRaw('
        CASE
            WHEN last_login_at IS NULL THEN "never"
            WHEN last_login_at >= NOW() - INTERVAL 30 DAY THEN "active"
            ELSE "inactive"
        END as activity_status
    ')
    ->get();

This keeps business reporting logic in one query and avoids loops over large datasets.

Example 3: Custom sorting with orderByRaw

Sort by business priority first, then by recency.

$tickets = DB::table('support_tickets')
    ->select('id', 'subject', 'priority', 'created_at')
    ->orderByRaw('
        FIELD(priority, "critical", "high", "medium", "low")
    ')
    ->orderByDesc('created_at')
    ->get();

FIELD(...) is a MySQL-specific helper that is perfect for custom order rules.

Example 4: Grouped reports with havingRaw

Find product categories with meaningful sales volume.

$categories = DB::table('order_items')
    ->join('products', 'products.id', '=', 'order_items.product_id')
    ->select('products.category_id')
    ->selectRaw('COUNT(*) as line_count')
    ->selectRaw('SUM(order_items.quantity * order_items.unit_price) as gross_sales')
    ->groupBy('products.category_id')
    ->havingRaw('SUM(order_items.quantity * order_items.unit_price) > ?', [10000])
    ->orderByDesc('gross_sales')
    ->get();

Notice the binding in havingRaw. Never interpolate dynamic values into raw SQL strings.

Example 5: Safe whereRaw with bindings

Use raw conditions with placeholders, not string concatenation.

$minAge = 21;
$country = 'KE';

$customers = DB::table('customers')
    ->whereRaw('TIMESTAMPDIFF(YEAR, date_of_birth, CURDATE()) >= ?', [$minAge])
    ->whereRaw('country_code = ?', [$country])
    ->get();

This pattern protects you from SQL injection and keeps query plans reusable.

Security: how to use raw SQL safely

When people say raw SQL is dangerous in Laravel, they usually mean unsafe interpolation.

Good:

DB::table('users')->whereRaw('email = ?', [$email])->first();

Bad:

DB::table('users')->whereRaw("email = '$email'")->first();

Use this checklist whenever you write raw SQL:

  • Always pass dynamic values as bindings (?, then array values).
  • Keep raw fragments small and focused.
  • Validate and sanitize upstream inputs as usual.
  • Never allow unchecked user input to control SQL keywords (column names, ORDER BY, etc.).

For framework-level guidance, see the official Laravel query documentation: Laravel Query Builder.

Performance notes for MySQL 8

Raw SQL can be faster or slower depending on how you write it. Treat performance as a measurement problem.

  1. Inspect execution plans with EXPLAIN for expensive queries.
  2. Add indexes that match your filter and join patterns.
  3. Avoid wrapping indexed columns in functions inside WHERE if you need index usage.
  4. Aggregate in SQL when possible, but avoid over-fetching rows.

You can profile query timings quickly in local development:

DB::listen(function ($query) {
    logger()->info('sql', [
        'sql' => $query->sql,
        'bindings' => $query->bindings,
        'time_ms' => $query->time,
    ]);
});

Testing raw query logic in Laravel 13

For reporting queries, test both shape and values.

it('returns active users first in priority report', function () {
    // Arrange test records...

    $rows = DB::table('users')
        ->select('id')
        ->orderByRaw('FIELD(status, "active", "invited", "disabled")')
        ->get();

    expect($rows)->not->toBeEmpty();
    expect($rows->first()->id)->toBe(1);
});

If you have a complex query, consider wrapping it in a repository method so your tests target one boundary instead of duplicating SQL across the codebase.

Common mistakes and fixes

MistakeWhy it hurtsBetter approach
Building SQL with string concatenationSQL injection riskUse placeholders and bindings
Overusing raw for simple filtersHarder to maintainUse query builder for basics
Raw expressions in many controllersLogic spread and hard to testMove query logic to service/repository
Ignoring query plansSlow pages and reportsUse EXPLAIN and proper indexes
Mixing business rules and SQL everywhereReduced readabilityKeep raw fragments focused and named

FAQ

Is DB::raw bad practice?

No. It is a tool. It becomes a problem only when overused or written unsafely. Use raw SQL when it improves clarity or performance, and keep standard queries in Eloquent/query builder.

Can I use bindings with raw helpers?

Yes. whereRaw, havingRaw, selectRaw, and similar helpers support bindings. Prefer them over manually injecting variables into SQL strings.

Should I use DB::raw for everything advanced?

Not always. Start with query builder or Eloquent. Use raw expressions for the parts that are truly SQL-specific.

Does DB::raw lock me into MySQL?

Sometimes. Functions like FIELD and DATE_FORMAT are MySQL-specific. If portability matters, isolate database-specific logic behind dedicated query methods.

Final takeaway

If you use Laravel db raw patterns carefully, they are an advantage, not a risk. In Laravel 13 with MySQL 8, raw expressions are especially useful for reporting, computed fields, and custom sorting, as long as you bind values and verify performance.

Next in this series: Create a New Laravel Project in 2026.