【问题标题】:Laravel DB::statement is not able to prepare values properly for the queries that involves column expressionsLaravel DB::statement 无法为涉及列表达式的查询正确准备值
【发布时间】:2019-11-25 21:47:09
【问题描述】:

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

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

这导致 SQL 如下:

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

^当在 mysql 解释器中使用时,这个查询工作得很好,但与 laravel 的 DB::statement 方法(它与未准备的方法 btw 一起工作)很糟糕。

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

它弹出的错误是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

另一位用户建议使用 unprepared 而不是分解为更简单的查询: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 解释器中就可以工作!

【问题讨论】:

标签: php mysql laravel laravel-5 eloquent


【解决方案1】:

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

更多参考: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)
        ]);

其中$flag$operator$integer 只是枚举。

更新:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-03
    • 2018-09-11
    • 1970-01-01
    • 2020-08-09
    • 1970-01-01
    • 1970-01-01
    • 2019-01-30
    相关资源
    最近更新 更多