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

MySQL 5.7+, JSON_SET value in nested path

对于最近的开发项目,我们使用 MySQL 5.7,因此我们可以利用最新的 JSON 功能...

我正在构建一个更新查询,其中嵌套的 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-object,如

{"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-对象,添加完整路径...如何做到这一点?

从 MySQL 版本 5.7.13 开始,假设您想要

的最终结果
{"test1": "Test 1", "test2": {"test3": "Test 3"}}

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

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

JSON_SET() Inserts or updates data in a JSON document and returns the result. Returns NULL if any argument is NULL or path, if given, does not locate an object.

意思是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 点符号,而是需要明确告诉 MySQL 该密钥作为 JSON 对象存在。

这类似于 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查询可以看出,在attributes的定义中没有提供$.test1,它保持原样,所以那些没有修改的属性可以从查询中省略.

Fyye,谢谢你的遮阳篷,非常感谢!由于数据没有固定的结构并且每条记录都可能不同,我需要一个解决方案,我可以在其中生成一个查询,该查询将在单个查询中自动生成总的 JSON-object。

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

当我执行更新时,我正在使用 JSON_MERGE 将一个空的 JSON-object 合并到数据库中的字段上,对于所有带有子节点的键,所以在数据库的 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 行时它们是如何执行的!

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

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

{"nodes": {}} 

执行更新时,我正在使用 JSON_MERGE 将空的 JSON-对象合并到数据库中的字段中,因此所有节点/子节点在 JSON-te 数据库中的字段,然后使用 JSON_SET 更新值。所以完整的查询看起来像这样:

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

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

现在,从 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": {}}} 的预期结果。