Laravel whereRaw vs selectRaw: Best Practices for Raw SQL in the Query Builder

Use whereRaw to inject raw SQL conditions into the WHERE clause for complex filtering, and selectRaw to add calculated columns or database-specific functions to the SELECT list, always binding parameters to prevent SQL injection.

When building complex database queries in the laravel/framework repository, developers often encounter scenarios that exceed the expressive capabilities of the standard query builder. Understanding how to safely implement a where clause with raw SQL statements using whereRaw, and how it architecturally differs from selectRaw, is essential for writing secure, maintainable Laravel applications.

How whereRaw Works in the Query Builder

The whereRaw method allows you to inject a raw SQL string directly into the WHERE clause of your query. According to the Laravel source code, this method is defined in src/Illuminate/Database/Query/Builder.php around line 1233.

When you call whereRaw, the builder pushes an array containing the raw SQL onto the $wheres collection:

['type' => 'raw', 'sql' => $sql, 'boolean' => $boolean]

The method then registers any supplied bindings via addBinding, ensuring that parameters remain separate from the SQL command itself. During query compilation, the Grammar::whereRaw method in src/Illuminate/Database/Query/Grammars/Grammar.php (around line 279) simply returns the raw SQL string while the grammar engine handles the bound parameters.

Binding Safety with whereRaw

Always use parameter bindings with whereRaw to protect against SQL injection. The framework stores these bindings under the 'where' key in the bindings array.

// Safe: uses parameter binding
$users = DB::table('users')
    ->whereRaw('JSON_EXTRACT(meta, "$.age") > ?', [30])
    ->get();

// Unsafe: string concatenation
$users = DB::table('users')
    ->whereRaw("JSON_EXTRACT(meta, '$.age') > $age")
    ->get();

How selectRaw Differs from whereRaw

While whereRaw targets the WHERE clause, selectRaw targets the SELECT list. Defined in src/Illuminate/Database/Query/Builder.php around line 348, this method creates a new Expression instance and adds it to the $columns collection.

The key architectural difference lies in the query phase each method affects:

  • whereRaw filters which rows are returned (the WHERE clause)
  • selectRaw determines what data is returned for each row (the SELECT clause)

Bindings for selectRaw are stored under the 'select' key, separate from WHERE bindings.

Practical selectRaw Usage

Use selectRaw for calculated columns, database-specific functions, or sub-queries in the SELECT list:

// Calculated columns with database functions
$stats = DB::table('orders')
    ->select('customer_id')
    ->selectRaw('COUNT(*) as order_count')
    ->selectRaw('SUM(total) as lifetime_value')
    ->groupBy('customer_id')
    ->get();

// Using database-specific JSON functions
$users = DB::table('users')
    ->select('id')
    ->selectRaw('JSON_EXTRACT(preferences, "$.theme") as theme')
    ->get();

Key Differences Between whereRaw and selectRaw

Understanding the distinct roles of these methods prevents architectural confusion when constructing queries.

Aspect whereRaw selectRaw
Target clause WHERE SELECT
Primary function Filters rows Defines return columns
Binding key 'where' 'select'
Compilation Grammar::whereRaw Expression object in $columns
Return impact Boolean filtering Column values/expressions

Best Practices for Raw SQL in Where Clauses

When you need functionality beyond the standard query builder methods, follow these guidelines to maintain security and portability.

Prefer Dedicated Builder Methods First

Before reaching for whereRaw, check if Laravel provides a dedicated method. The framework includes specialized methods for common operations that handle database-specific syntax automatically:

  • whereJsonContains() for JSON queries
  • whereColumn() for comparing columns
  • whereBitwise() for bitwise operations
  • whereDate(), whereMonth(), etc. for date filtering

Only use whereRaw when the builder lacks native support for your specific condition.

Always Use Parameter Bindings

Never concatenate user input or variables directly into raw SQL strings. Laravel's binding system protects against SQL injection by separating data from commands.

// Correct: parameterized query
$query->whereRaw('price > ? AND quantity < ?', [$minPrice, $maxQty]);

// Incorrect: string interpolation (vulnerable)
$query->whereRaw("price > $minPrice AND quantity < $maxQty");

Mix Raw Methods with Standard Builder Chains

whereRaw and selectRaw integrate seamlessly with other query builder methods. You can combine them with standard where(), join(), and orderBy() calls:

$results = DB::table('products')
    ->select('id', 'name')
    ->selectRaw('(price - cost) as margin')
    ->where('active', true)
    ->whereRaw('stock_level < reorder_point')
    ->orderBy('margin', 'desc')
    ->get();

Document Database-Specific Dependencies

Raw SQL often uses database-specific functions like JSON_EXTRACT (MySQL) or STRING_AGG (PostgreSQL). When using these, document the dependency in comments or documentation to ensure portability concerns are visible during database migrations.

Summary

  • whereRaw injects raw SQL into the WHERE clause via src/Illuminate/Database/Query/Builder.php, storing bindings under the 'where' key and compiling through Grammar::whereRaw in src/Illuminate/Database/Query/Grammars/Grammar.php.
  • selectRaw adds raw expressions to the SELECT list, creating Expression objects stored in the $columns collection with bindings under the 'select' key.
  • Security: Always use parameter bindings with whereRaw to prevent SQL injection; never concatenate variables directly into raw SQL strings.
  • Portability: Prefer dedicated builder methods (whereJsonContains, whereColumn) before resorting to raw SQL, and document any database-specific functions used in raw expressions.

Frequently Asked Questions

Is whereRaw safe from SQL injection?

whereRaw is safe only when you use parameter bindings. The method accepts an optional second argument for bindings, which Laravel processes through addBinding in src/Illuminate/Database/Query/Builder.php and separates from the SQL command during compilation in Grammar::whereRaw. If you concatenate variables directly into the SQL string, you bypass this protection and become vulnerable to SQL injection attacks.

When should I use whereRaw instead of regular where clauses?

Use whereRaw only when Laravel's dedicated query builder methods cannot express your condition. According to the src/Illuminate/Database/Query/Builder.php source, you should first check for native methods like whereJsonContains, whereColumn, whereBitwise, or date-specific methods. Reserve whereRaw for complex JSON path queries, bitwise operations on unsupported databases, or custom database functions that lack builder abstractions.

How do I combine whereRaw with other query builder methods?

whereRaw integrates seamlessly with standard builder methods because it returns the Builder instance. You can chain whereRaw with where(), orWhere, join(), orderBy(), and other methods just like any other query builder method. The raw condition gets added to the $wheres collection alongside standard conditions, and the grammar compiler processes them in order during SQL generation in src/Illuminate/Database/Query/Grammars/Grammar.php.

What is the difference between whereRaw and havingRaw?

whereRaw filters rows before aggregation in the WHERE clause, while havingRaw filters results after aggregation in the HAVING clause. In src/Illuminate/Database/Query/Builder.php, whereRaw adds conditions to the $wheres array processed during the initial row filtering phase, whereas havingRaw adds to the $havings array processed after GROUP BY operations. Use whereRaw to filter which rows enter the calculation, and havingRaw to filter aggregated results like SUM(total) > 1000.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →