如何让查询生成器将其原始 SQL 查询输出为字符串?

Posted

技术标签:

【中文标题】如何让查询生成器将其原始 SQL 查询输出为字符串?【英文标题】:How do I get the query builder to output its raw SQL query as a string? 【发布时间】:2013-08-16 16:11:47 【问题描述】:

给定以下代码:

DB::table('users')->get();

我想获取上面的数据库查询生成器将生成的原始 SQL 查询字符串。在此示例中,它将是 SELECT * FROM users

我该怎么做?

【问题讨论】:

Laravel Eloquent ORM 获取原始查询:echo User::where('status', 1)->toSql(); 我正在为 Laravel - Telescope 使用一个数据包,它会记录所有查询并执行更多操作。 所有这些答案都是从 DB 类而不是模型开始的。如果您在模型类上调用方法怎么办? 【参考方案1】:

QueryBuilder 实例上使用toSql() 方法。

DB::table('users')->toSql() 会返回:

从“用户”中选择 *

这比连接事件侦听器更容易,并且还可以让您在构建查询时随时检查查询的实际外观。

注意:此方法适用于查询构建器或 Eloquent,但使用 toSql() 代替 first()get()。使用此方法无法同时运行查询并获取 SQL。

【讨论】:

我认为这是在 Laravel 之外使用 Eloquent 时最简单的方法 @Stormsson 这是不可能的,因为 php 从来没有将绑定的查询替换为它们的值。要完整获取查询,您需要从 mysql 记录它们。这里有更多信息:***.com/questions/1786322/… @Stormsson 你可以使用getBindings 方法。这将返回绑定,以便将它们绑定到 SQL 语句。 使用绑定查询$query = \DB::table('users')->where('id', 10); $sql = str_replace_array('?', $query->getBindings(), $query->toSql()); dd($sql); 对于 laravel 6 $query = \DB::table('users')->where('id', 10); Str::replaceArray('?', $query->getBindings(), $query->toSql()); 输出 select * from users where id = 10【参考方案2】:

要将上次运行的查询输出到屏幕,您可以使用以下命令:

DB::enableQueryLog(); // Enable query log

// Your Eloquent query executed by using get()

dd(DB::getQueryLog()); // Show results of log

我相信最近的查询将位于数组的底部。

你会得到类似的东西:

array(1) 
  [0]=>
  array(3) 
    ["query"]=>
    string(21) "select * from "users""
    ["bindings"]=>
    array(0) 
    
    ["time"]=>
    string(4) "0.92"
  

(感谢Joshua's下面的评论。)

【讨论】:

嗯,我不确定,但你可以使用作曲家包***.com/a/17339752/813181 完成你想要的 使用Log 类将其输出到应用程序的日志可能会更好:Log::debug(DB::getQueryLog()) 您可能需要启用它,因为它现在默认关闭。您可以使用此命令将其临时打开:DB::enableQueryLog(); 我试过你的答案。我试过的是DB::enableQueryLog(); dd(DB::getQueryLog()); 但它只返回[].... 如果你有多个数据库,你可能需要做DB::connection('database')->getQueryLog()【参考方案3】:

DB::QueryLog() 仅在您使用$builder->get() 执行查询后才有效。

如果您想在执行查询之前或不执行查询之前获取原始查询,可以使用$builder->toSql() 方法。

获取原始 SQL 并替换“?”的示例具有实际绑定值:

$query = str_replace(array('?'), array('\'%s\''), $builder->toSql());
$query = vsprintf($query, $builder->getBindings());
dump($query);

$result = $builder->get();

或者您可以故意触发错误,例如,通过使用不存在的表或列。然后就可以在异常信息中看到生成的查询了。

【讨论】:

这是迄今为止最好的答案,简单明了。谢谢:) 单线:$query = vsprintf(str_replace(array('?'), array('\'%s\''), $builder->toSql()), $builder->getBindings()); 这应该作为原生函数包含在框架中。谢谢 请注意,如果您的查询已经包含百分号(例如 LIKE 查询或格式化日期时),这将不起作用。你需要先用双百分号转义那些。 执行此操作时是否存在安全问题?绑定是否来自$builder->getBindings()【参考方案4】:

您可以收听“illuminate.query”事件。在查询之前添加以下事件监听器:

Event::listen('illuminate.query', function($query, $params, $time, $conn) 
 
    dd(array($query, $params, $time, $conn));
);

DB::table('users')->get();

这将打印出如下内容:

array(4) 
  [0]=>
  string(21) "select * from "users""
  [1]=>
  array(0) 
  
  [2]=>
  string(4) "0.94"
  [3]=>
  string(6) "sqlite"

【讨论】:

我在 Laravel 4 中调用未定义的方法 Illuminate\Database\Query\Builder::listen() 谢谢,太好了。值得注意的是 dd 是一个函数,它产生一个 Dump 给定变量并结束脚本的执行以及导入事件,包括 use Illuminate\Support\Facades\Event; @radtek:您可以简单地使用use Event; 而不是use Illuminate\Support\Facades\Event;,因为它是facade。【参考方案5】:

如果你试图在不使用 Laravel 的情况下使用 Illuminate 获取日志:

\Illuminate\Database\Capsule\Manager::getQueryLog();

你也可以像这样启动一个快速函数:

function logger()

    $queries = \Illuminate\Database\Capsule\Manager::getQueryLog();
    $formattedQueries = [];
    foreach ($queries as $query) :
        $prep = $query['query'];

        foreach ($query['bindings'] as $binding) :

            if (is_bool($binding)) 
                $val = $binding === true ? 'TRUE' : 'FALSE';
             else if (is_numeric($binding)) 
                $val = $binding;
             else 
                $val = "'$binding'";
            

            $prep = preg_replace("#\?#", $val, $prep, 1);
        endforeach;
        $formattedQueries[] = $prep;
    endforeach;
    return $formattedQueries;

编辑

更新版本似乎默认禁用查询日志记录(上面返回一个空数组)。要重新开启,在初始化 Capsule Manager 时,抓取一个连接实例并调用 enableQueryLog 方法

$capsule::connection()->enableQueryLog();

再次编辑

考虑到实际问题,您实际上可以执行以下操作来转换当前单个查询而不是所有先前的查询:

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

【讨论】:

我从查询 "name = ["name":"rifat"]" 得到这种类型的返回,我需要做什么才能只得到 "name = rifat"? 我会打印出你的绑定,看起来你传递的是一个数组而不是一个字符串 这是一个有用的开始,但它似乎忽略了在参数化值周围添加单引号,例如当我传递像 'US/Eastern' 这样的字符串时。 @Ryan 这是真的,所以我说quick function。我相信底层代码将使用准备(php.net/manual/en/mysqli.prepare.php)方法,这就是为什么只需要?。您可以php.net/manual/en/function.is-numeric.php 来确定是否将输入封装在单引号内。 @LukeSnowden 你的答案是天才!我终于花时间试用了您的新版本(我在上面进行了编辑以包含您的 is_numeric 想法),并且它有效!我喜欢这个。谢谢。【参考方案6】:

eloquent 中有一个获取查询字符串的方法。

toSql()

在我们的例子中,

 DB::table('users')->toSql(); 

返回

select * from users

是返回 SQL 查询字符串的确切解决方案..希望这有帮助...

【讨论】:

查询绑定呢?例如当你这样做时->where('foo', '=', 'bar') 栏不会显示在 sql 中【参考方案7】:
$data = User::toSql();
echo $data; //this will retrun select * from users. //here User is model

【讨论】:

这样更精确、更可控,更符合问题的需要。 感谢您的评论。 您可以附加->toSql(),就像模型后面有更多参数一样。例如User::where('id', 1)->toSql()【参考方案8】:

如果你使用 laravel 5.1 和 MySQL,你可以使用我制作的这个功能:

/*
 *  returns SQL with values in it
 */
function getSql($model)

    $replace = function ($sql, $bindings)
    
        $needle = '?';
        foreach ($bindings as $replace)
            $pos = strpos($sql, $needle);
            if ($pos !== false) 
                if (gettype($replace) === "string") 
                     $replace = ' "'.addslashes($replace).'" ';
                
                $sql = substr_replace($sql, $replace, $pos, strlen($needle));
            
        
        return $sql;
    ;
    $sql = $replace($model->toSql(), $model->getBindings());
    
    return $sql;

作为输入参数,您可以使用其中任何一个

Illuminate\Database\Eloquent\Builder

Illuminate\Database\Eloquent\Relations\HasMany

照亮\数据库\查询\构建器

【讨论】:

Answer 已改进以包括在 cmets 中所做的所有备注。非常感谢。【参考方案9】:

这是我可以建议任何人调试 eloquent last query 或 final query 的最佳解决方案,尽管这也已讨论过:

// query builder
$query = DB::table('table_name')->where('id', 1);

// binding replaced
$sql = str_replace_array('?', $query->getBindings(), $query->toSql());

// for laravel 5.8^
$sql = Str::replaceArray('?', $query->getBindings(), $query->toSql());

// print
dd($sql);

【讨论】:

您需要在代码顶部使用以下行:对于初学者使用 Illuminate\Support\Str;使用 Str 类【参考方案10】:

首先您需要通过调用启用查询日志:

DB::enableQueryLog();

在使用 DB 外观查询之后,您可以编写:

dd(DB::getQueryLog());

输出如下:

array:1 [▼
  0 => array:3 [▼
    "query" => "select * from `users` left join `website_user` on `users`.`id` = `website_user`.`user_id` left join `region_user` on `users`.`id` = `region_user`.`user_id` left ▶"
    "bindings" => array:5 [▶]
    "time" => 3.79
  ]
]

【讨论】:

非常有帮助的答案 嗨,我使用了 $result = DB::select('select * from sqrt_user_modules where user_id = :id', ['id' => $user]); DB::enableQueryLog();但没有得到任何输出 dd(DB::getQueryLog()); 我们是否需要包含任何库 第 1 步:DB::enableQueryLog();第 2 步:$result = DB::select('select * from sqrt_user_modules where user_id = :id', ['id' => $user]);第 3 步:dd(DB::getQueryLog());【参考方案11】:

'macroable' 替换以获取带有绑定的 SQL 查询。

    AppServiceProvider boot() 方法中添加下面的宏函数。

    \Illuminate\Database\Query\Builder::macro('toRawSql', function()
        return array_reduce($this->getBindings(), function($sql, $binding)
            return preg_replace('/\?/', is_numeric($binding) ? $binding : "'".$binding."'" , $sql, 1);
        , $this->toSql());
    );
    

    为 Eloquent Builder 添加一个别名。 (Laravel 5.4+)

    \Illuminate\Database\Eloquent\Builder::macro('toRawSql', function()
        return ($this->getQuery()->toRawSql());
    );
    

    然后像往常一样调试。 (Laravel 5.4+)

    例如查询生成器

    \Log::debug(\DB::table('users')->limit(1)->toRawSql())
    

    例如雄辩的建设者

    \Log::debug(\App\User::limit(1)->toRawSql());
    

注意:从 Laravel 5.1 到 5.3,由于 Eloquent Builder 不使用 Macroable 特征,因此无法动态添加 toRawSql 别名到 Eloquent Builder。按照下面的例子来实现同样的效果。

例如Eloquent Builder (Laravel 5.1 - 5.3)

\Log::debug(\App\User::limit(1)->getQuery()->toRawSql());

【讨论】:

哎呀,我来晚了。只想使用宏提交答案。这是最好的答案。应该是公认的答案:D 您可以在基础模型的范围内抽象出后者【参考方案12】:

第一种方式:

您只需使用 toSql() 方法即可完成以下操作,

$query = DB::table('users')->get();

echo $query->toSql();

如果它不起作用,您可以从 laravel documentation 设置。

第二种方式:

另一种方法是

DB::getQueryLog()

但如果它返回一个空数组,那么默认情况下它被禁用visit this,

只需使用DB::enableQueryLog() 启用即可:)

如需更多信息,请访问 Github Issue 了解更多信息。

希望对你有帮助:)

【讨论】:

【参考方案13】:

从 Laravel 5.8.15 开始,查询构建器 now has dddump 方法可以做到

DB::table('data')->where('a', 1)->dump();

【讨论】:

谢谢。 dd 效果很好。 DB::table('data')->where('a', 1)->dd(); 比列出的其他答案更好。【参考方案14】:

最简单的方法是犯故意的错误。例如,我想查看以下关系的完整 SQL 查询:

 public function jobs()
        
            return $this->belongsToMany(Job::class, 'eqtype_jobs')
                   ->withPivot(['created_at','updated_at','id'])
                   ->orderBy('pivot_created_at','desc');
        

我只是为了让一列找不到,这里我选择created_at,然后通过添加尾随s将其更改为created_ats

public function jobs()
            
                return $this->belongsToMany(Job::class, 'eqtype_jobs')
                       ->withPivot(['created_ats','updated_at','id'])
                       ->orderBy('pivot_created_at','desc');
            

因此,调试器将返回以下错误:

(4/4) ErrorException SQLSTATE[42S22]:找不到列:1054 未知 “字段列表”中的列“eqtype_jobs.created_ats”(SQL:选择 jobs.*, eqtype_jobs.set_id 作为pivot_set_id, eqtype_jobs.job_id as pivot_job_id, eqtype_jobs.created_ats 作为pivot_created_atseqtype_jobs.updated_at 作为 pivot_updated_at, eqtype_jobs.id as pivot_id from jobs inner 在jobs.id 上加入eqtype_jobs = eqtype_jobs.job_id eqtype_jobs.set_id = 56 order by pivot_created_at desc limit 20 偏移量 0) (查看: /home/said/www/factory/resources/views/set/show.blade.php)

以上错误信息返回带有错误的完整 SQL 查询

SQL: select  jobs.*, eqtype_jobs.set_id as pivot_set_id,  eqtype_jobs.job_id as pivot_job_id, eqtype_jobs.created_ats as pivot_created_ats, eqtype_jobs.updated_at as  pivot_updated_at, eqtype_jobs.id as pivot_id from jobs inner join eqtype_jobs on jobs.id = eqtype_jobs.job_id where  eqtype_jobs.set_id = 56 order by pivot_created_at desc limit 20 offset 0

现在,只需从 created_at 中删除多余的 s 并在任何 SQL 编辑器(例如 phpMyAdmin SQL 编辑器)中测试此 SQL!

注意:

该解决方案已使用 Laravel 5.4 进行测试。

【讨论】:

这是迄今为止最好的答案!很简单! :) 这不会显示带有绑定的查询,即绑定将显示为:id @ShanthaKumara 确实,我不知道你使用的 Laravel 的版本或配置是什么。但是,我的答案中的每个 sn-p 或代码都是从 Laravel 5.4 项目的实际代码输出中复制和粘贴的。【参考方案15】:

在我看来,这将是初学者最好的方法:

echo "<pre>";
print_r($query->toSql());
print_r($query->getBindings());

这里也有描述。 https://***.com/a/59207557/9573341

【讨论】:

【参考方案16】:

将此函数添加到您的应用程序并简单地调用。

function getQuery($sql)
        $query = str_replace(array('?'), array('\'%s\''), $sql->toSql());
        $query = vsprintf($query, $sql->getBindings());     
        return $query;

输出: "select * from user where lang = 'en' and status = '1' order by updated_at desc limit 25 offset 0"

【讨论】:

【参考方案17】:

使用调试栏包

composer require "barryvdh/laravel-debugbar": "2.3.*"

【讨论】:

【参考方案18】:

从 laravel 5.2 开始。您可以使用DB::listen 来执行查询。

DB::listen(function ($query) 
    // $query->sql
    // $query->bindings
    // $query->time
);

或者,如果您想调试单个 Builder 实例,则可以使用 toSql 方法。

DB::table('posts')->toSql(); 

【讨论】:

listen这个东西很有用,在运行查询之前声明它,并在方法中转储sql和绑定。不完美,但比其他答案更快/更容易。【参考方案19】:

已经回答了很多信息,只要我需要在执行之前输出 sql 查询,我就会发布我自己的发现。

考虑以下示例:

$user = DB::table('user')->where('id',1);
echo $user->toSql();

echo $user->toSql() = 这只会输出原始查询,但不会显示传递的参数。

要输出带有传递参数的查询,我们可以像这样使用 laravel getBindings() 和 helper str_replace_array

$queryWithParam = str_replace_array('?',$user->getBindings(),$user->toSql());
echo $queryWithParam;

希望这也有帮助。

【讨论】:

【参考方案20】:

要查看 Laravel 执行的查询,请使用 laravel 查询日志

DB::enableQueryLog();

$queries = DB::getQueryLog();

【讨论】:

【参考方案21】:

这是我放置在我的基础模型类中的函数。只需将查询构建器对象传递给它,就会返回 SQL 字符串。

function getSQL($builder) 
  $sql = $builder->toSql();
  foreach ( $builder->getBindings() as $binding ) 
    $value = is_numeric($binding) ? $binding : "'".$binding."'";
    $sql = preg_replace('/\?/', $value, $sql, 1);
  
  return $sql;

【讨论】:

【参考方案22】:

试试这个:

$results = DB::table('users')->toSql();
dd($results);

注意:get() 已替换为 toSql() 以显示原始 SQL 查询。

【讨论】:

【参考方案23】:

你可以使用 toSql 方法——最简单的方法

DB::table('users')->toSql();

如果您的查询中有绑定并且希望查看带有绑定的查询。你不能使用这样的东西:

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

$sql_with_bindings = str_replace_array('?', $query->getBindings(), $query->toSql());

dd($sql_with_bindings);

【讨论】:

【参考方案24】:

适用于 laravel 5.5.X

如果您想接收应用程序执行的每个 SQL 查询,您可以使用 listen 方法。此方法对于记录查询或调试很有用。你可以在服务提供者中注册你的查询监听器:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    
        DB::listen(function ($query) 
            // $query->sql
            // $query->bindings
            // $query->time
        );
    

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    
        //
    

Source

【讨论】:

【参考方案25】:

我的做法,基于日志视图,只需要修改文件app/Providers/AppServiceProvider.php

    将此代码添加到app/Providers/AppServiceProvider.php
/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()

    //
    DB::listen(function ($query) 
        $querySql = str_replace(['?'], ['\'%s\''], $query->sql);
        $queryRawSql = vsprintf($querySql, $query->bindings);
        Log::debug('[SQL EXEC]', [
                "raw sql"  => $queryRawSql,
                "time" => $query->time,
            ]
        );
    );

    我的sql句柄代码:
$users = DB::table('users')
    ->select(DB::raw('count(*) as user_count, username '))
    ->where('uid', '>=', 10)
    ->limit(100)
    ->groupBy('username')
    ->get()
;
dd($users);
    见日志storage/logs/laravel-2019-10-27.log
[2019-10-27 17:39:17] local.DEBUG: [SQL EXEC] "raw sql":"select count(*) as user_count, username  from `users` where `uid` >= '10' group by `username` limit 100","time":304.21 

【讨论】:

【参考方案26】:

您可以使用此包获取加载页面时正在执行的所有查询

https://github.com/barryvdh/laravel-debugbar

【讨论】:

当您没有查询错误时,该包很好。如果你有一个 SQL 错误,它不会显示任何东西【参考方案27】:

尽管我很喜欢这个框架,但我讨厌它表现得像废话。

DB::enableQueryLog() 完全没用。 DB::listen 同样没用。当我说$query-&gt;count()时,它显示了部分查询,但如果我说$query-&gt;get(),则无话可说。

似乎始终有效的唯一解决方案是故意在 ORM 参数中放置一些语法或其他错误,例如不存在的列/表名称,在调试模式下在命令行上运行您的代码,它会吐出最后,完整的 frickin 查询的 SQL 错误。否则,如果从 Web 服务器运行,希望错误会出现在日志文件中。

【讨论】:

查询日志至少对我来说很好用。您的应用程序中应该还有其他一些错误【参考方案28】:

打印最后一个查询

DB::enableQueryLog();

$query        = DB::getQueryLog();
$lastQuery    = end($query);
print_r($lastQuery);

【讨论】:

【参考方案29】:

如果你正在使用 tinker 并且想要记录形成的 SQL 查询,你可以这样做

$ php artisan tinker
Psy Shell v0.9.9 (PHP 7.3.5 — cli) by Justin Hileman
>>> DB::listen(function ($query)  dump($query->sql); dump($query->bindings); dump($query->time); );
=> null
>>> App\User::find(1)
"select * from `users` where `users`.`id` = ? limit 1"
array:1 [
  0 => 1
]
6.99
=> App\User #3131
     id: 1,
     name: "admin",
     email: "admin@example.com",
     created_at: "2019-01-11 19:06:23",
     updated_at: "2019-01-11 19:06:23",
   
>>>

【讨论】:

【参考方案30】:

如果你没有使用 Laravel 而是使用 Eloquent 包,那么:

use \Illuminate\Database\Capsule\Manager as Capsule;
use \Illuminate\Events\Dispatcher;
use \Illuminate\Container\Container;

$capsule = new Capsule;

$capsule->addConnection([
    // connection details
]);
// Set the event dispatcher used by Eloquent models... (optional)
$capsule->setEventDispatcher(new Dispatcher(new Container));

// Make this Capsule instance available globally via static methods... (optional)
$capsule->setAsGlobal();

// Setup the Eloquent ORM...(optional unless you've used setEventDispatcher())
$capsule->bootEloquent();

// Listen for Query Events for Debug
$events = new Dispatcher;
$events->listen('illuminate.query', function($query, $bindings, $time, $name)

    // Format binding data for sql insertion
    foreach ($bindings as $i => $binding) 
        if ($binding instanceof \DateTime) 
            $bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
         else if (is_string($binding)) 
            $bindings[$i] = "'$binding'";`enter code here`
        
    

    // Insert bindings into query
    $query = str_replace(array('%', '?'), array('%%', '%s'), $query);
    $query = vsprintf($query, $bindings);

    // Debug SQL queries
    echo 'SQL: [' . $query . ']';
);

$capsule->setEventDispatcher($events);

【讨论】:

以上是关于如何让查询生成器将其原始 SQL 查询输出为字符串?的主要内容,如果未能解决你的问题,请参考以下文章

我如何将 SQL 原始查询重写为 Laravel 查询生成器

为啥这个 Django 原始 SQL 查询不返回输出?

如何在执行查询之前从Laravel的查询生成器获取原始查询字符串?

如何将原始 SQL 查询转换为 Silverstripe SQLQuery 抽象层

如何从 Java 中的 Oracle SQL 选择中获取原始脚本输出而不是查询结果

如何使用 CakePHP 查询生成器生成 SQL 函数调用?