【问题标题】:Laravel how to get query with bindings?Laravel如何通过绑定获取查询?
【发布时间】:2015-02-03 12:59:48
【问题描述】:

我有一些查询需要使用查询生成器传递给另一个查询

$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');
})

结果应该是

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

但绑定没有通过,这迫使我这样做

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

有时可能不安全。如何获取带绑定的查询?

【问题讨论】:

  • some_field 是整数吗?如果是这样,您可以使用join(',', array_map('intval', [1,2,30])) 来确保数组只包含整数。
  • 验证不是个案。更多关于代码的清晰性。

标签: laravel eloquent


【解决方案1】:

简单优雅的解决方案:

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

(如果您使用非默认连接,请在 foreach 命令中使用DB::connection('yourConn')->getQueryLog())。

【讨论】:

    【解决方案2】:
    $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】:

      Douglas.Sesar 的回答为基础。

      我发现我还需要将绑定放在单引号中,以便能够轻松地将其粘贴到我的数据库 IDE 中。

      $sql = $query->toSql();
      $bindings = $query->getBindings();
      
      $sql_with_bindings = preg_replace_callback('/\?/', function ($match) use ($sql, &$bindings) {
          return "'" . array_shift($bindings) . "'";
      }, $sql);
      

      【讨论】:

        【解决方案4】:

        这是一个非常古老的问题(2015 年),但由于这是我得到的第一个 Google 结果,我认为也值得给出我的解决方案,以防它对下一个人有用。

        Eloquent(我认为是 5.7 以后的版本,我还没有测试过更新或更早的版本)有一种方法可以更改 Builder 的 from 以包装子查询:

        # 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);
        }
        

        但这需要一个已经存在的\Illuminate\Database\Query\Builder 实例。为了做一个空的,你可以这样做:

        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");
        
        

        这将保证绑定得到正确处理,因为它们将由 Eloquent 自己处理。

        【讨论】:

          【解决方案5】:

          你可以这样做:

          $escapedBindings = array();
          
          foreach($query->getBindings() as $item) {$escapedBindings[] = '"'.$item.'"';}
          
          $sql_with_bindings = Str::replaceArray('?', $escapedBindings, $query->toSql());
          

          【讨论】:

            【解决方案6】:

            将所有插入绑定的查询输出到日志从最慢到最快的查询排序

                \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');
                }
            

            【讨论】:

              【解决方案7】:

              您可以将下面的代码块定义为辅助函数并在需要的地方使用。 它将用引号绑定数字和字符串值。

              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());
              }
              

              例子:

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

              输出:

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

              【讨论】:

                【解决方案8】:

                Laravel 现在可以直接在 Builder 上进行调试!!!

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

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

                输出

                "select * from `users` where `age` = ?"
                [
                    0 => "18"
                ]
                

                【讨论】:

                  【解决方案9】:

                  查看Builder 类中的getBindings() 方法

                  getBindings()

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

                  【讨论】:

                  • 使用绑定获取 SQL $sql_with_bindings = str_replace_array('?', $query-&gt;getBindings(), $query-&gt;toSql());
                  • 如何收藏评论? :D
                  • 请注意,str_replace_array() 已弃用。而是使用 Str 实用程序类:use Illuminate\Support\Str; Str::replaceArray('?', $query-&gt;getBindings(), $query-&gt;toSql()).
                  • @EnnioSousa,这可能非常危险,因为 getBindings 提供的绑定是未转义的。
                  • @EnnioSousa 请查看 Soulriser 上面关于 str_replace_array() 函数的评论
                  【解决方案10】:

                  以下函数通过将 ? 括起来为 '?' 来确保生成的 SQL 不会将绑定与列混淆

                      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);
                      }
                  

                  【讨论】:

                  • 它可以工作,但不能逃脱绑定,对某些人来说可能有问题。
                  【解决方案11】:

                  我创建了这个函数。它是部分的,可能是未涵盖的参数,对我来说已经足够了。
                  非常欢迎在评论中添加您的改进!

                  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;
                  }
                  

                  【讨论】:

                  • +1 这是唯一有意义的事情。我只是没有得到最后一部分,我看到的问题是,如果我在某个表中有一个名为selected 的列,那么在转换后会显示SELECTed。也许它应该用$pretify 参数默认为false 进行标记,因为在生产中我不在乎它的外观;)
                  • 哦,我会将case "boolean": 添加到integerdouble。原生整数 1 和 boolean true 与 false 和 0 中的相同。
                  【解决方案12】:

                  由于其他答案没有正确引用表达式,这是我的方法。它使用了转义功能,属于当前数据库连接。

                  将问号一一替换为对应的绑定,绑定通过array_shift()从$bindings中获取,在过程中消耗数组。请注意,必须通过引用传递 $bindings 才能使其工作。

                  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());
                  }
                  

                  【讨论】:

                    【解决方案13】:

                    如果您想从查询日志中获取包含绑定的已执行查询:

                    \DB::enableQueryLog();
                    \DB::table('table')->get();
                    dd(str_replace_array('?', \DB::getQueryLog()[0]['bindings'], 
                          \DB::getQueryLog()[0]['query']));
                    

                    【讨论】:

                    • 可以循环 DB::getQueryLog() 以获取所有带有 op 绑定的查询!谢谢你。
                    • 非常重要!如果您使用与默认连接不同的连接,则必须使用DB::connection('myConn')-&gt;enableQueryLog(); 指定它。
                    • 函数 str_replace_array 已弃用。您可以改用 Str 类实用程序:\Str::replaceArray
                    【解决方案14】:
                    public static function getQueries(Builder $builder)
                    {
                        $addSlashes = str_replace('?', "'?'", $builder->toSql());
                        return vsprintf(str_replace('?', '%s', $addSlashes), $builder->getBindings());
                    }
                    

                    【讨论】:

                    • 这就是我要找的东西
                    • 原来使用'LIKE'时'%s'的使用会与通配符冲突
                    • 遇到了同样的问题。你可以简单地使用str_replace(['?', '%'], ["'?'", '%%'], $builder-&gt;toSql()),就可以了。
                    【解决方案15】:

                    这里都说明了..... https://ajcastro29.blogspot.com/2017/11/laravel-join-derived-tables-properly.html

                    我为那个东西创建了一个范围查询。我认为它也可以在宏中..

                    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;
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2019-02-13
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2019-12-25
                      • 1970-01-01
                      相关资源
                      最近更新 更多