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

时间:2016-07-11 17:00:09

标签: php mysql json mysql-json

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

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

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

当我执行此查询时,attributes-field包含数据

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

4 个答案:

答案 0 :(得分:6)

假设你想要

的最终结果
attributes

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

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

  

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

含义MySQL可以添加$.test2,但由于$.test2.test3不是对象,MySQL无法添加到$.test2

因此,您需要通过执行以下操作将mysql> SELECT * FROM testing; +----+---------------------+ | id | attributes | +----+---------------------+ | 1 | {"test1": "Test 1"} | +----+---------------------+ 1 row in set (0.00 sec) 定义为json对象。

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)
$a = (object) ['test1' => 'Test 1'];
$a->test2->test3 = 'Test 3';

//PHP Warning:  Creating default object from empty value

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

这类似于PHP也定义了不存在的对象属性值。

$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_SET

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

答案 1 :(得分:2)

Fyrye,感谢awnser,非常感谢!由于数据没有固定的结构,并且每个记录可能不同,我需要一个解决方案,我可以生成一个查询,在一个查询中自动生成总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行时的预测形式!

答案 2 :(得分: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": {}}}的预期结果。

答案 3 :(得分:0)

在像你们所有人一样的地方搜索之后,我发现了这里列出的最佳解决方案: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还会在设置子节点时创建父节点?