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 值的主要内容,如果未能解决你的问题,请参考以下文章