MySQL 5.7+,嵌套路径中的 JSON_SET 值

Posted

技术标签:

【中文标题】MySQL 5.7+,嵌套路径中的 JSON_SET 值【英文标题】:MySQL 5.7+, JSON_SET value in nested path 【发布时间】:2016-11-13 17:18:15 【问题描述】:

对于最近的一个开发项目,我们使用的是 mysql 5.7,因此我们可以利用最新的 JSON 函数...

我正在构建一个 UPDATE 查询,其中应将嵌套的 json 对象插入/添加到 JSON 类型的属性列中,请参阅下面的查询。

UPDATE `table` SET `table`.`name` = 'Test',
    `table`.`attributes` = JSON_SET(
         `table`.`attributes`,
         "$.test1", "Test 1",
         "$.test2.test3", "Test 3"
     )

当我执行这个查询时,属性字段包含数据

"test1": "Test 1" 

而不是想要的

"test1", "Test 1", "test2": "test3", "Test 3"

也尝试使用 JSON_MERGE,但是当我多次执行它时,它会创建一个类似于

的 JSON 对象
"test1": ["Test 1", "Test 1", "Test 1"... etc.], "test2": "test3": ["Test 3", "Test 3", "Test 3"... etc.]

那么,当节点不存在时,JSON_SET 不起作用? JSON_MERGE 合并到无穷大?

JSON 对象中使用的键可以由用户定义,因此不可能为所有可能的键创建一个空的 JSON 对象。我们真的需要在每个 UPDATE 查询之前执行 JSON_CONTAINS / JSON_CONTAINS_PATH 查询以确定我们是否需要使用 JSON_SET 或 JSON_MERGE / JSON_APPEND?

我们正在寻找一种让查询始终有效的方法,因此当给出"$.test4.test5.test6" 时,它将扩展当前的 JSON 对象,添加完整路径...如何做到这一点?

【问题讨论】:

【参考方案1】:

从 MySQL 5.7.13 版开始,假设您希望得到以下结果

"test1": "Test 1", "test2": "test3": "Test 3"

在您的示例中,正在更新的 attributes 列设置为 "test1": "Test 1"

查看您最初的UPDATE 查询,我们可以看到$.test2.test3 不存在。 所以不能设置为

JSON_SET() 在 JSON 文档中插入或更新数据并返回 结果。如果任何参数为 NULL,则返回 NULL,或者如果给出路径,则返回 NULL 找不到对象。

意思是MySQL可以添加$.test2,但是由于$.test2不是一个对象,所以MySQL不能添加到$.test2.test3

因此,您需要通过执行以下操作将 $.test2 定义为 json 对象。

mysql> SELECT * FROM testing;
+----+---------------------+
| id | attributes          |
+----+---------------------+
|  1 | "test1": "Test 1" |
+----+---------------------+
1 row in set (0.00 sec)
mysql> UPDATE testing
    -> SET attributes = JSON_SET(
    ->     attributes,
    ->     "$.test1", "Test 1",
    ->     "$.test2", JSON_OBJECT("test3", "Test 3")
    -> );
Query OK, 1 row affected (0.03 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> SELECT * FROM testing;
+----+---------------------------------------------------+
| id | attributes                                        |
+----+---------------------------------------------------+
|  1 | "test1": "Test 1", "test2": "test3": "Test 3" |
+----+---------------------------------------------------+
1 row in set (0.00 sec)

因此,您需要明确告诉 MySQL 该键作为 JSON 对象存在,而不是依赖于 MySQL 点表示法。

这类似于 php 如何定义不存在的对象属性值。

$a = (object) ['test1' => 'Test 1'];
$a->test2->test3 = 'Test 3';

//PHP Warning:  Creating default object from empty value

要消除错误,您需要先将$a->test2 定义为对象。

$a = (object) ['test1' => 'Test 1'];
$a->test2 = (object) ['test3' => 'Test 3'];

或者,您可以在使用点符号之前测试和创建对象,以设置值。尽管对于较大的数据集,这可能是不可取的。

mysql> UPDATE testing
    -> SET attributes = JSON_SET(
    ->     attributes, "$.test2", IFNULL(attributes->'$.test2', JSON_OBJECT())
    -> ),
    -> attributes = JSON_SET(
    ->     attributes, "$.test4", IFNULL(attributes->'$.test4', JSON_OBJECT())
    -> ),
    -> attributes = JSON_SET(
    ->     attributes, "$.test4.test5", IFNULL(attributes->'$.test4.test5', JSON_OBJECT())
    -> ),
    -> attributes = JSON_SET(
    ->     attributes, "$.test2.test3", "Test 3"
    -> );
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> SELECT * FROM testing;
+----+---------------------------------------------------------------------------+
| id | attributes                                                                |
+----+---------------------------------------------------------------------------+
|  1 | "test1": "Test 1", "test2": "test3": "Test 3", "test4": "test5":  |
+----+---------------------------------------------------------------------------+
1 row in set (0.00 sec)

尽管在任何一种情况下,如果未提供原始数据,JSON_OBJECT 函数调用都会清空嵌套对象的属性值。但是从上一个JSON_SET 查询中可以看到,$.test1 没有在attributes 的定义中提供,并且它保持不变,因此可以在查询中省略那些未修改的属性。

【讨论】:

这不仅让处理 JSON 文档变得非常痛苦吗?我的意思是在结构未知且嵌套很深的情况下。究竟什么时候会转向 JSON IMO。【参考方案2】:

现在,从 MySQL 5.7.22 版开始,最简单的方法是像这样使用JSON_MERGE_PATCH

UPDATE `table` SET `attributes` = 
JSON_MERGE_PATCH(`attributes`, '"test2": "test3": "Test 3", "test4": "test5": ')

它给出了"test1": "Test 1", "test2": "test3": "Test 3", "test4": "test5": 的预期结果,如您的示例所示。

【讨论】:

JSON_MERGE_PATCH 将从结果中删除键,第二个对象中的键值为 JSON null 文字。 doc【参考方案3】:

Fyrye,感谢您提供的遮阳篷,非常感谢!由于数据没有固定的结构并且每条记录都可能不同,因此我需要一个解决方案,我可以生成一个查询,该查询会在单个查询中自动生成整个 JSON 对象。

我非常喜欢您使用JSON_SET(attributes, "$.test2", IFNULL(attributes->'$.test2',JSON_OBJECT())) 方法的解决方案。因为我继续搜索,所以我自己也想出了一个使用JSON_MERGE函数的解决方案。

当我执行更新时,我使用JSON_MERGE 将一个空的 JSON 对象合并到数据库中的字段中,对于所有带有子节点的键,所以它们在 JSON 字段中可用数据库,然后使用JSON_SET 更新值。所以完整的查询如下所示:

UPDATE table SET
    -> attributes = JSON_MERGE(
    -> attributes, '"test2": , "test4": "test5": '),
    -> attributes = JSON_SET(attributes, "$.test2.test3", "Test 3");

执行此查询后,结果将如下所示:

 mysql> SELECT * FROM testing;
 +----+---------------------------------------------------------------------------+
 | id | attributes                                                                |
 +----+---------------------------------------------------------------------------+
 |  1 | "test1": "Test 1", "test2": "test3": "Test 3", "test4": "test5":  |
 +----+---------------------------------------------------------------------------+
 1 row in set (0.00 sec)

我目前不知道哪种方法更好,目前都可以。将来会进行一些速度测试,以检查它们在 1 更新 10.000 行时的表现!

【讨论】:

这是真正的痛苦 IMO。我已提交功能请求bugs.mysql.com/bug.php?id=84167。也许如果您有时间可以通过单击效果我按钮来支持它:)或评论。【参考方案4】:

像你们中的许多人一样到处搜索后,我找到了这里列出的最佳解决方案:https://forums.mysql.com/read.php?20,647956,647969#msg-647969

来自网站: 他节点和子节点,但不包含任何数据...... 所以在上面的例子中,对象会是这样的:

"nodes":  

执行更新时,我使用JSON_MERGE 将空 JSON 对象合并到数据库中的字段中,因此所有节点/子节点在 te 数据库中的 JSON 字段中可用,之后,使用JSON_SET 更新值。所以完整的查询如下所示:

UPDATE table SET attributes = JSON_MERGE(attributes, '"nodes": '), attributes = JSON_SET(attributes, "$.nodes.node2", "Node 2") 

目前,这是可行的。 但这是一个奇怪的解决方法。也许这可以在以前的 MySQL 版本中查看,所以JSON_SET 在设置子节点时也会创建父节点?

【讨论】:

嗨 Gregor,这篇文章已经有 2 年历史了。在 MySQL 报告此问题/问题后(请参阅:bugs.mysql.com/bug.php?id=84167),已经在 MySQL 中为 JSON 进行了一些更改/创建了新函数。查看 JSON_MERGE_PATCH(自 MySQL 5.7.22 起可用),这可能是解决您问题的更好方法,请参阅:dev.mysql.com/doc/refman/5.7/en/…

以上是关于MySQL 5.7+,嵌套路径中的 JSON_SET 值的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 5.7 主从复制(主从同步)

mysql 5.7 启动脚本

MySQL 5.7 源码中的目录结构

mysql 5.5升级到5.7版本操作流程

银行 Rehat7无网部署Mysql.5.7

linux mysql 5.6 能升级5.7 吗