85

I have some query that I need to pass to another query using query builder

$query = DB::table('table')->whereIn('some_field', [1,2,30])->toSql();

Model::join(DB::raw("({$query}) as table"), function($join) {
    $join->on('model.id', '=', 'table.id');
})

which should results with

Select * from model join (select * from table where some_field in (1,2,30)) as table on model.id = table.id

but the bindings are not passed, which force me to do

$query = DB::table('table')->whereRaw('some_field in ('. join(',', [1,2,30]) .')')->toSql();

what can be unsafe at times. How can I get the query with bindings?

3
  • Is some_field an integer? If so you could use join(',', array_map('intval', [1,2,30])) to make sure the array only contains integers.
    – Marwelln
    Commented Dec 5, 2014 at 11:10
  • Validation is not a case. Is more about code clarity. Commented Dec 5, 2014 at 11:18
  • You can use toRawSql() with Laravel 10 to get the whole query including the bindings Commented Jul 2, 2023 at 18:59

21 Answers 21

100

Update (July 2023):

As of Laravel 10.15.0 you can use dumpRawSql() or ddRawSql()

DB::table('table')->whereIn('some_field', [1,2,3])->ddRawSql();

Original:

Check out the getBindings() method on the Builder class

getBindings()

$query = DB::table('table')->whereIn('some_field', [1,2,30]);

$sql = $query->toSql();

$bindings = $query->getBindings();
7
  • 128
    To get SQL with Bindings $sql_with_bindings = str_replace_array('?', $query->getBindings(), $query->toSql()); Commented Aug 1, 2018 at 18:07
  • 61
    Note that str_replace_array() is deprecated. Instead use the Str utility class: use Illuminate\Support\Str; Str::replaceArray('?', $query->getBindings(), $query->toSql()).
    – Soulriser
    Commented Jul 12, 2019 at 17:13
  • 7
    @EnnioSousa, this can be quite dangerous because the bindings getBindings gives are unescaped. Commented Jul 23, 2019 at 13:42
  • 3
    @EnnioSousa please look at Soulriser's comment above concerning the str_replace_array() function Commented Nov 5, 2019 at 18:08
  • 2
    @androidu str_replace_array us deorecated, use Illuminate\Support\Str; Str::replaceArray() instead. Commented Nov 6, 2019 at 14:09
36

Laravel now offers debugging directly on your Builder!!!

https://laravel.com/docs/queries#debugging

\App\User::where('age', '18')->dump();
\App\User::where('age', '18')->dd();

Outputs

"select * from `users` where `age` = ?"
[
    0 => "18"
]
31
public static function getQueries(Builder $builder)
{
    $addSlashes = str_replace('?', "'?'", $builder->toSql());
    return vsprintf(str_replace('?', '%s', $addSlashes), $builder->getBindings());
}
3
  • 2
    this is what i was looking for
    – ManojKiran
    Commented Apr 3, 2019 at 19:19
  • 1
    Turns out that the usage of '%s' will conflict with wildcards when using 'LIKE Commented Jul 30, 2019 at 18:15
  • 2
    Had the same issue. You can simply use str_replace(['?', '%'], ["'?'", '%%'], $builder->toSql()), that'll do the trick.
    – robbash
    Commented Feb 6, 2021 at 3:06
10

You can define below code block as helper function and use wherever required. It will bind numeric as well as string value with quotations.

public static function getSqlWithBindings($query)
{
    return vsprintf(str_replace('?', '%s', $query->toSql()), collect($query->getBindings())->map(function ($binding) {
        return is_numeric($binding) ? $binding : "'{$binding}'";
    })->toArray());
}

Example:

$query = Document::where('model', 'contact')->where('model_id', '1');
dd(Document::getSqlWithBindings($query));

Output:

"select * from `document` where `model` = 'contact' and `model_id` = 1"
6

Building upon Douglas.Sesar's answer.

I found I also needed to put the bindings in single quotations to be able to easily paste it into my database IDE.

$sql = $query->toSql();
$bindings = $query->getBindings();

$sql_with_bindings = preg_replace_callback('/\?/', function ($match) use ($sql, &$bindings) {
    return json_encode(array_shift($bindings));
}, $sql);
1
  • Hey I used this and I liked it - However, the quoting did not work well with FALSE - so I fixed it with return json_encode(array_shift($bindings)); - I think I will make this edit.
    – amurrell
    Commented Apr 15, 2022 at 21:32
5
$sqlQuery = Str::replaceArray(
    '?',
    collect($query->getBindings())
        ->map(function ($i) {
            if (is_object($i)) {
                $i = (string)$i;
            }
            return (is_string($i)) ? "'$i'" : $i;
        })->all(),
    $query->toSql());
3

The following function ensures the resulting SQL doesn't confuse bindings with columns by enclosing the ? to be '?'

    public static function getFinalSql($query)
    {
        $sql_str = $query->toSql();
        $bindings = $query->getBindings();

        $wrapped_str = str_replace('?', "'?'", $sql_str);

        return str_replace_array('?', $bindings, $wrapped_str);
    }
2
  • It works but it does not escape the bindings, Could be problematic for some. Commented Oct 22, 2019 at 22:37
  • \Str::replaceArray use instead str_replace_array
    – Soyab Badi
    Commented Aug 5, 2022 at 10:43
2

If you want to get an executed query including bindings from the query log:

\DB::enableQueryLog();
\DB::table('table')->get();
dd(str_replace_array('?', \DB::getQueryLog()[0]['bindings'], 
      \DB::getQueryLog()[0]['query']));
3
  • One can loop DB::getQueryLog() to get all queries with bindings op to that point! Thanks for this. Commented Jun 26, 2019 at 7:56
  • Very important! If you use a connection which is different from the default one, you must specify it using DB::connection('myConn')->enableQueryLog();.
    – guyaloni
    Commented Aug 26, 2019 at 12:04
  • 1
    Function str_replace_array is deprecated. You can use instead Str class utility: \Str::replaceArray
    – guyaloni
    Commented Sep 23, 2021 at 16:33
2

I created this function. It is partial, might be parameters which are not covered, for me it was enough.
More than welcomed to add your improvements in a comment!

function getFullSql($query) {
  $sqlStr = $query->toSql();
  foreach ($query->getBindings() as $iter=>$binding) {

    $type = gettype($binding);
    switch ($type) {
      case "integer":
      case "double":
        $bindingStr = "$binding";
        break;
      case "string":
        $bindingStr = "'$binding'";
        break;
      case "object":
        $class = get_class($binding);
        switch ($class) {
          case "DateTime":
            $bindingStr = "'" . $binding->format('Y-m-d H:i:s') . "'";
            break;
          default:
            throw new \Exception("Unexpected binding argument class ($class)");
        }
        break;
      default:
        throw new \Exception("Unexpected binding argument type ($type)");
    }

    $currentPos = strpos($sqlStr, '?');
    if ($currentPos === false) {
      throw new \Exception("Cannot find binding location in Sql String for bundung parameter $binding ($iter)");
    }

    $sqlStr = substr($sqlStr, 0, $currentPos) . $bindingStr . substr($sqlStr, $currentPos + 1);
  }

  $search = ["select", "distinct", "from", "where", "and", "order by", "asc", "desc", "inner join", "join"];
  $replace = ["SELECT", "DISTINCT", "\n  FROM", "\n    WHERE", "\n    AND", "\n    ORDER BY", "ASC", "DESC", "\n  INNER JOIN", "\n  JOIN"];
  $sqlStr = str_replace($search, $replace, $sqlStr);

  return $sqlStr;
}
2
  • +1 This is the only thing that makes sense. I just don't get the last part, the problem I see there is that if I would have a column called selected in some table, it would say SELECTed after the conversion. Maybe it should be flagged with a $pretify parameter default to false, because in production I don't care how it looks ;)
    – dbf
    Commented Oct 27, 2020 at 21:51
  • 1
    Oh and I would add case "boolean": to integer and double. Natively integer 1 and boolean true are the same as in false and 0.
    – dbf
    Commented Oct 27, 2020 at 21:54
2

You can do something like this:

$escapedBindings = array();

foreach($query->getBindings() as $item) {$escapedBindings[] = '"'.$item.'"';}

$sql_with_bindings = Str::replaceArray('?', $escapedBindings, $query->toSql());
2

This is a very old question (2015), but since this is the first Google result I got I think it's worth to give my solution as well, in case it's useful for the next person.

Eloquent (5.7 onwards I think, I haven't tested more recent or earlier versions) has a method to change a Builder's from to wrap a subquery:

# Taken from Illuminate/Database/Query/Builder.php - Line 272
public function fromSub($query, $as) {
    [$query, $bindings] = $this->createSub($query);

    return $this->fromRaw('('.$query.') as '.$this->grammar->wrapTable($as), $bindings);
}

This however requires an already existing instance of \Illuminate\Database\Query\Builder. In order to make an empty one, you can do:

use Illuminate\Database\Capsule\Manager as DB;

$fancy = DB::table("videogames")->where("uses_sdl2", 1);
$empty = DB::table(null);

# Wrap the fancy query and set it as the "from" clause for the empty one
# NOTE: the alias is required
$empty = $empty->fromSub($fancy, "performant_games");

This will warranty that bindings are treated correctly, since they'll be handled by Eloquent itself.

0
2

Since the other answers do not properly quote the expressions, here is my approach. It uses the escaping function that belongs to the current database connection.

It replaces the question marks one by one with the corresponding binding, which is retrieved from $bindings via array_shift(), consuming the array in the process. Note, that $bindings has to be passed by reference for this to work.

function getSql($query)
{
        $bindings = $query->getBindings();

        return preg_replace_callback('/\?/', function ($match) use (&$bindings, $query) {
            return $query->getConnection()->getPdo()->quote(array_shift($bindings));
        }, $query->toSql());
}
2

Raw Query Output With Bindings is Coming to Laravel 10 on the next tagged v10.x release (as of 7/3/2023).

Example:

User::where('email', '[email protected]')->toRawSql();

Which will then dump the following results:

"SELECT * FROM users WHERE email = '[email protected]'"
1

Simple and elegant solution:

foreach (DB::getQueryLog() as $q) {
    $queryStr = \Str::replaceArray('?', $q['bindings'], $q['query']);
    echo $queryStr . ";\n";
}

(if you use a non-default connection, use DB::connection('yourConn')->getQueryLog() in the foreach command).

1

Laravel 10 can now return raw queries with bindings!

User::where('email', '[email protected]')->toRawSql();
0

Output to the log all queries with inserted bindings sorted from the slowest query to the fastest:

    \DB::enableQueryLog();

    // Put here your queries 
    $query = DB::table('table')->whereIn('some_field', [1,2,30]); 
    $query2 = DB::table('table2')->where('some_field', '=', 10); 


    $logQueries = \DB::getQueryLog();
    usort($logQueries, function($a, $b) {
        return $b['time'] <=> $a['time'];
    });

    foreach ($logQueries as $item) {
        \Log::info(str_replace_array('?', $item['bindings'], $item['query']));
        \Log::info($item['time']. ' ms');
    }
0

You can add the following code snippet to the boot() method in your AppServiceProvider.php file:

use Illuminate\Database\Query\Builder;

public function boot()
{
    /// ......
    
    Builder::macro('sql', function () {
        $query = $this;
        $bindings = $query->getBindings();

        return preg_replace_callback('/\?/', function ($match) use (&$bindings, $query) {
            return $query->getConnection()->getPdo()->quote(array_shift($bindings));
        }, $query->toSql());
    });
}

After adding the code to the boot() method, you can utilize it in any query as follows:

// Example usage:
Model::sql();

By following these steps, you'll be able to incorporate the provided code snippet into your AppServiceProvider file and utilize the sql() method in your queries.

0

A neat solution in Laravel 10 and later is to use toRawSql() instead of toSql() which prints the complete query including the bindings.

$query = DB::table('table')->whereIn('some_field', [1,2,30])->toRawSql();

Model::join(DB::raw("({$query}) as table"), function($join) {
    $join->on('model.id', '=', 'table.id');
})
0

My generic solution for DB debugging is this:

DB::listen(function (QueryExecuted $query) {
  $sql_with_bindings = Str::replaceArray('?', $query->bindings, $query->sql);
  Log::info(sprintf("SQL WITH DATA: %s", $sql_with_bindings));
});

Depending on you situation, you might consider adding a:

if (config('app.debug') == true) {
}

around this if you want to avoid debug logs in production...

0

I have a little hack to view the actual running query

User::where('id', 123)->first()

just change the column name to break the query and you will see the complete query in the error

User::where('ida', 123)->first()

It will break the query and throw an error screen 🤣 you can see which query is running their

Note: only for local development.

fun fact you can do this in every framework

-3

It is all explained here..... https://ajcastro29.blogspot.com/2017/11/laravel-join-derived-tables-properly.html

I created a scope query for that thing. I think it can also be in macros..

public function scopeJoinDerived($query, $derivedQuery, $table, $one, $operator = null, $two = null, $type = 'inner', $where = false)
{
    $query->join(DB::raw("({$derivedQuery->toSql()}) as `{$table}`"), $one, $operator, $two, $type, $where);
    $join = last($query->getQuery()->joins);
    $join->bindings =  array_merge($derivedQuery->getBindings(), $join->bindings);

    return $query;
}
0

Not the answer you're looking for? Browse other questions tagged or ask your own question.