首页 > 解决方案 > Laravel DB::statement 无法为涉及列表达式的查询正确准备值

问题描述

我想知道 DB::statement 和 DB::unprepared 查询之间出现的问题,所以显然 DB::statement 方法不适用于对列本身执行表达式的查询,如下所示:

更新标签 SET ? = ? + 2 在哪里?> ? 和 user_id = ? 和 tree_id = ?

这导致 SQL 为:

更新标签 SET rgt = rgt + 2 WHERE rgt > 2 AND user_id = 1 AND tree_id = 1

^此查询在 mysql 解释器中使用时工作得非常好,但与 laravel 的 DB::statement 方法一起使用时会变得很糟糕(顺便说一句,它与未准备的方法一起工作得很好)。

这种不匹配背后的原因是什么?

它弹出的错误很SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax奇怪,因为它的语法正确并且已经可以在 mysql 上正常工作。

所以语句方法是这样做的:

    {
        return $this->run($query, $bindings, function ($query, $bindings) {
            if ($this->pretending()) {
                return true;
            }
            $statement = $this->getPdo()->prepare($query);
            $this->bindValues($statement, $this->prepareBindings($bindings));
            $this->recordsHaveBeenModified();
            return $statement->execute();
        });
    }

而毫无准备的:

    {
        return $this->run($query, [], function ($query) {
            if ($this->pretending()) {
                return true;
            }
            $this->recordsHaveBeenModified(
                $change = ($this->getPdo()->exec($query) === false ? false : true)
            );
            return $change;
        });
    }

我认为问题在于prepare方法。

在其他地方也无法查明问题的问题参考:

用户建议对高级查询使用未准备的方法?此查询尽可能基本:https ://laracasts.com/discuss/channels/general-discussion/raw-queries-1?page=0

另一位用户建议使用未准备的而不是分解为更简单的查询:https ://laracasts.com/discuss/channels/laravel/why-does-my-raw-query-not-work-inside-laravel-db?page= 2

奖励问题:此外,如果您知道任何 Eloquent 处理此查询的方法,那就太棒了!(从某种意义上说,如何通过本机、where、update 等方法处理列级表达式。)

编辑:这个问题与How to do update query on laravel fluent using db::raw不重复,因为它仍然涉及对列值进行硬编码。DB::raw('column * 2')

我的问题纯粹是因为不做任何硬编码语句而让 laravel 做绑定。

例如:这个查询 -

DB::update(DB::raw(
            'UPDATE tags SET ? = ? ? ? WHERE ? >= ? AND user_id = ? AND tree_id = ?'),
            [$flag, $flag, $operator, $integer, $flag, $controlIndex, $user_id, $tree_id]
        );

产生如下错误:SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '? = ? ? ? WHERE ? >= ? AND user_id = ? AND tree_id = ?' at line 1 (SQL: UPDATE tags SET rgt = rgt + 2 WHERE rgt >= 1 AND user_id = 1 AND tree_id = 1)

只需查看 SQL 查询:UPDATE tags SET rgt = rgt + 2 WHERE rgt >= 1 AND user_id = 1 AND tree_id = 1- 如果放入 mysql 解释器,这完全合法且有效!

标签: phpmysqllaravellaravel-5eloquent

解决方案


好的,经过更多检查,我发现您不能绑定表/列名,而只能绑定查询中的值

更多参考:https ://stackoverflow.com/a/15990488/6303162

要了解为什么绑定表(或列)名称不起作用,您必须了解预准备语句中的占位符是如何工作的:它们不是简单地替换为(适当转义的)字符串,而是执行生成的 SQL。相反,要求“准​​备”语句的 DBMS 会针对如何执行该查询提出完整的查询计划,包括它将使用哪些表和索引,无论您如何填写占位符,这些计划都是相同的。

看起来你也不能绑定数据库名称——所以它不是 Laravel 或 PHP 的缺陷,而只是数据库的设计方式。

SELECT name FROM my_table WHERE id = :value 的计划将与您替换 :value 的计划相同,但看似相似的 SELECT name FROM :table WHERE id = :value 无法计划,因为 DBMS 不知道您使用什么表'实际上要从中进行选择。

因此,应该做的是有一个列的白名单,并确保在执行查询之前根据它进行过滤。为开发人员提供了防止任何 SQL 注入的任务。我原以为 Laravel 可能会实现基于列的绑定,但它可能有自己的优点和缺点。

  1. 运行查询以查找正在完成列绑定的所有列。
  2. 将白名单视为 Model 类本身中的某种变量,就像$fillable存在一样。

尽管如此,我能想出的最优雅的解决方案如下:

DB::table('tags')->where([
            ['user_id', $user_id],
            ['tree_id', $tree_id],
            [$flag, '>', $controlIndex]
        ])->update([
            $flag => DB::raw($flag . $operator . $integer)
        ]);

Where$flag$operator只是$integer枚举。

更新:

当有人向我推荐incrementanddecrement方法时,我在一些聊天论坛上问了同样的问题,这完全符合问题的解决方案!所以我更新的查询调用如下所示:

Tags::where([
            ['user_id', $user_id],
            ['tree_id', $tree_id],
            [$flag, '>', $controlIndex]])
    ->crement($flag, $integer, $operator);

crement只是附加到Tags模型的本地范围方法,它指示调用是递增还是递减。


推荐阅读