php - 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 解释器,这完全合法且有效!
解决方案
好的,经过更多检查,我发现您不能绑定表/列名,而只能绑定查询中的值
更多参考: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 可能会实现基于列的绑定,但它可能有自己的优点和缺点。
- 运行查询以查找正在完成列绑定的所有列。
- 将白名单视为 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
枚举。
更新:
当有人向我推荐increment
anddecrement
方法时,我在一些聊天论坛上问了同样的问题,这完全符合问题的解决方案!所以我更新的查询调用如下所示:
Tags::where([
['user_id', $user_id],
['tree_id', $tree_id],
[$flag, '>', $controlIndex]])
->crement($flag, $integer, $operator);
crement
只是附加到Tags
模型的本地范围方法,它指示调用是递增还是递减。
推荐阅读
- git - 如何提交对 docker 镜像所做的更改
- html - 在 Apple 的 WebKit webView 中加载自定义字体的问题
- sql - SQL - 获取每个 PlayerID 的最小日期
- typescript - 扩展一个可由用户定义的接口
- ansible - 合并到嵌套的字典数组中
- python - 如何在 python Dataframe 中将“2011-04-29 00:00:00”转换为“29-Apr-2011”
- python - 根据两个不同列的日期获取交叉连接表的唯一记录时,如何提高逻辑速度/内存?
- java - 运行 IDE Android Studio 需要 Java 11 或更高版本
- javascript - ReactJs 点击后状态不更新
- javascript - Javascript reportValidity 未按预期工作