Laravel 的查询构建器 JSON 选择器 `field->key` 导致语法错误

Posted

技术标签:

【中文标题】Laravel 的查询构建器 JSON 选择器 `field->key` 导致语法错误【英文标题】:Laravel's query builder JSON selector `field->key` causes syntax error 【发布时间】:2018-02-27 04:01:06 【问题描述】:

所以,我想通过比较某个 ID 与 data 列来查询 Laravel 中的 notifications 表。这是data 列的样子:


    "Message": "some message",
    "id": 3

现在,我需要选择 ID 等于 3 的所有通知。这是我尝试的方法:

DB::table('notifications')->where('data->id', '3')->get();

但这会引发以下错误:

SQLSTATE[42000]:语法错误或访问冲突:1064 你有一个 SQL 语法错误;检查与您对应的手册 MariaDB 服务器版本,用于在 '>'$."id"' = 附近使用正确的语法 ?在第 1 行 (SQL: select * from notifications where data->'$."id"' = 3)

我在这里疯了,谁能帮帮我?

【问题讨论】:

Laravel 将 JSON 存储为文本,所以不能这样查询。您需要更改存储此信息的方式才能查询它,否则您将需要像data LIKE '%"id": 3%' 这样进行全文搜索,但是这种查询非常慢。 看看这个:***.com/questions/44669673/… 【参考方案1】:

问题是只有mysql支持->操作符,那么在MariaDB上查询会失败,但是:

MariaDBMySQL 都支持 JSON_EXTRACT 函数,它解析 JSON 并从中获取值。

你可以通过这样的查询来解决它:

SELECT * FROM notifications WHERE JSON_EXTRACT(`notifications.data`, "$.id") = 5

要使用Laravel's Query Builder,您需要使用DB::raw 方法:

DB::table('notifications')->where(DB::raw('JSON_EXTRACT(`notifications.data`, "$.id")'), '=', 3);

请尝试一下,如果出错了告诉我。

注意:JSON_EXTRACT() 仅适用于MySQL >= 5.7MariaDB >= 10.2.3

感谢@Sepehr,我不知道-> 是MySQL 运算符,也不存在JSON 类型。 :-)

【讨论】:

即使您的答案“围绕”问题,它也不能回答问题。您关于 Laravel 将 JSON 存储为字符串 is false 的声明,它是 JSON 类型的字段,而不是字符串类型的字段。除此之外,从 Laravel 5.3 开始,您可以定位 JSON 类型的字段键 using the -> notation,而不是在您建议的原始查询中使用 JSON_EXTRACT 您仍然没有回答实际问题。 JSON type 和 -> operator 都存在。但是您在存储类型上是对的; Laravel 的 JSON 类型字段被创建为 longtext 类型字段并且是字符串。但是您仍然应该能够使用箭头符号查询它们而不会出现语法错误。它在docs。 实际的问题是,获得built by Laravel's MySQL grammar 的-> extraction operator 在对MariaDB 执行时会抛出该语法错误。 我刚刚确认了这一点,MariaDB 不支持->。然而,同样适用于 MySQL 安装。 @sepehr,正如我在回答中所说,JSON_EXTRACT 解决了问题,对吧? :-)【参考方案2】:

问题在于->,我认为您有一个名为$datavar,其中包含一些集合。 如果是这样,那么正确的方法是:

DB::table('notifications')->where($data->id, '3')->get();

或者,如果您有一个与 通知 表相关的模型,那么:

Notification::where($data->id, '3')->get();

如果 Model 被称为 Notification(遵循 Eloquent 约定)

但如果你试图找到所有 ID 都等于 3,那么只需:

DB::table('notifications')->where('id', '3')->get();

【讨论】:

OP 问题中的 -> 表示法是 special Laravel notation 用于访问 JSON 字段的键。最终会翻译成MySQL对应的operator。话虽如此,您的答案与问题完全无关。欢迎来到 SO。 我确实了解 JSON 我想我误解了这个问题【参考方案3】:

您的查询没有问题。这是你的环境。

问题

Laravel 的 MySqlGrammar 将字段名称中的 field->key 表示法(在 Laravel 端)转换为 field->'$.key' 样式的提取(在 MySQL 端):

/**
 * Wrap the given JSON selector.
 *
 * @param  string  $value
 * @return string
 */
protected function wrapJsonSelector($value)

    $path = explode('->', $value);

    $field = $this->wrapValue(array_shift($path));

    $path = collect($path)->map(function ($part) 
        return '"'.$part.'"';
    )->implode('.');

    // Here:
    return sprintf('%s->\'$.%s\'', $field, $path);

我刚刚确认 MariaDB 不支持 -> extraction operator 作为 JSON_EXTRACT() 函数的别名。但是,相同的查询适用于普通 MySQL 5.7 服务器。

假设这个test 表:

╔════╤══════════════════╗
║ id │ payload          ║
╟────┼──────────────────╢
║  1 │ "a": 1, "b": 2 ║
╚════╧══════════════════╝

使用-> 提取运算符的查询:

SELECT payload->"$.b" FROM test;

在 MariaDB 10.2.8 上失败,而在 MySQL 5.7.19 服务器上生成正确的2

解决方案

正确的解决方案取决于您在生产中使用的内容。

替换 MariaDB

如果您使用的是 MySQL,请在您的开发环境中将 MariaDB 替换为 MySQL。在由 homebrew 管理的 macOS 机器上,它很简单:

brew services stop mysql
brew uninstall mariadb
brew install mysql
brew services start mysql

您的数据将保持不变。

重写您的查询

但是,如果您在生产环境中使用 MariaDB,则需要重写查询以将 JSON_EXTRACT() 函数用作 Elias already mentioned。如您所见,您需要更详细地使用 Laravel API。

上面的查询是:

SELECT JSON_EXTRACT(payload, "$.b") FROM test;

【讨论】:

【参考方案4】:

我为 json 支持制作了 Laravel MariaDB 驱动程序。在这里获取:ybr-nx/laravel-mariadb

【讨论】:

有趣!这应该是核心。

以上是关于Laravel 的查询构建器 JSON 选择器 `field->key` 导致语法错误的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 查询构建器:如何访问 JSON 键/值对

如何使用 Laravel 的流畅查询构建器选择计数?

如何使用 Laravel 查询构建器跨表选择多个列?

Laravel - 使用 Eloquent 查询构建器在选择中添加自定义列

Laravel 查询构建器 - 从另一个表中选择前 1 行

Laravel 的数据库查询构建器