【问题标题】:Update a nested value from a Json field从 Json 字段更新嵌套值
【发布时间】:2021-08-29 17:00:15
【问题描述】:

考虑这张表:

DROP TABLE IF EXISTS `example`;
CREATE TABLE `example` (
  `id` int NOT NULL AUTO_INCREMENT,
  `content` json NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

还有这些行:

INSERT INTO example(content) 
VALUES (
  '[  { "date": "1617210148", "name": "John",  "status": "0" },
{ "date": "1617210148", "name": "Jack",  "status": "0" },
{ "date": "1617210148", "name": "Henry",  "status": "0" }]'
);

我想更新键 status 的值,其中 name = Jack 为 1

结果将是: { "date": "1617210148", "name": "Jack", "status": "1" }

如何在 SQL 查询中使用 JSON_REPLACE() 或 JSON_SET() 执行此操作(我的目标是进行部分更新,因为我使用的是 MySQL 8.0.25)?

【问题讨论】:

  • 出于好奇,您为什么选择对这些数据使用 JSON?它是一个相似文档的数组(每个文档中的相同字段),它可以作为第二个表,其中包含一组行而不是数组,以及普通列而不是对象字段。然后在单行上更新名称会很容易。
  • 我计划解析数组以填充由 datatables.net javascript 管理的表中的 html 行,以管理客户端过滤和分页。我更喜欢选择一个大字段,而不是让我的用户在数十万行中选择数百行。对数据的唯一操作是通过 Ajax 更新此键值。除此之外,我不需要查询这些数据。
  • 您可以创建一个端点,将收集的数据作为 JSON 文档返回,即使这些行以规范化的方式存储在数据库中也是如此。例如,我使用 JSON_ARRAYAGG() 和 JSON_OBJECT() 完成了这项工作。这使得客户端很容易使用它,但在数据库中保持一种更有效的格式。
  • 最终我并不真正关心 json 格式的数据,如果我不清楚,对不起。我的意思是我更喜欢将所有数据存储在一个字段中,无论是 JSON,而不是在一个表中进行 SELECT 查询,如果我必须对数据进行规范化,那么表中会有数十万行。由于我上面提到的密钥的频繁更新,缓存也不是一个真正的选择。在速度和查询成本方面,我认为使用单个 json 字段会更好......也就是说,如果我能找到一种方法来更新嵌套键的值。

标签: mysql sql-update mysql-json


【解决方案1】:

这很尴尬,用 MySQL 的 JSON 函数几乎不可能。

您可以使用 JSON_REPLACE() 或 JSON_SET(),但两者都要求您知道要更改的字段的路径。所以在这种情况下,我们可以看到数组元素是$[1],但如果你不知道,你就不能使用这个解决方案。

mysql> select json_pretty(json_replace(content, '$[1].status', '1')) as j 
  from example\G
*************************** 1. row ***************************
j: [
  {
    "date": "1617210148",
    "name": "John",
    "status": "0"
  },
  {
    "date": "1617210148",
    "name": "Jack",
    "status": "1"
  },
  {
    "date": "1617210148",
    "name": "Henry",
    "status": "0"
  }
]

以前在 Stack Overflow 上出现过类似您的问题,例如 JSON update single value in MySQL table。这种情况下的解决方案取决于您知道伪记录存在于哪个数组元素中。

您可以使用 JSON_SEARCH() 获取 JSON 元素的路径,但您只能按值搜索,而不能按键/值对搜索。如果“Jack”出现在其他字段中,也可以找到。

mysql> select json_unquote(json_search(content, 'one', 'Jack')) as path from example;
+-----------+
| path      |
+-----------+
| $[1].name |
+-----------+

要搜索键/值对,您需要使用 JSON_TABLE() 并且需要升级到 MySQL 8.0。这并没有告诉你元素的路径,它只允许你从数组中返回特定的行。

mysql> select j.* from example cross join json_table(content, '$[*]' columns(
  date int unsigned path '$.date',
  name varchar(10) path '$.name',
  status int path '$.status')
) as j where name = 'Jack';
+------------+------+--------+
| date       | name | status |
+------------+------+--------+
| 1617210148 | Jack |      0 |
+------------+------+--------+

这里有个技巧:您可以提取name 字段,然后将其转换为这些值的数组:

mysql> select json_extract(content, '$[*].name') as a from example;
+---------------------------+
| a                         |
+---------------------------+
| ["John", "Jack", "Henry"] |
+---------------------------+

然后你可以搜索该数组以获取数组位置:

mysql> select json_search(json_extract(content, '$[*].name'), 'one', 'Jack') as root from example;
+--------+
| root   |
+--------+
| "$[1]" |
+--------+

一些取消引用并添加.status,您可以获得要更新的字段的完整路径:

mysql> select concat(json_unquote(json_search(json_extract(content, '$[*].name'), 'one', 'Jack')), '.status') as path from example;
+-------------+
| path        |
+-------------+
| $[1].status |
+-------------+

现在在 JSON_SET() 调用中使用它:

mysql> select json_pretty( 
    json_set(content,
     concat(json_unquote(json_search(json_extract(content, '$[*].name'), 'one', 'Jack')), '.status'), 
     '1')) as newcontent 
  from example\G
*************************** 1. row ***************************
newcontent: [
  {
    "date": "1617210148",
    "name": "John",
    "status": "0"
  },
  {
    "date": "1617210148",
    "name": "Jack",
    "status": "1"
  },
  {
    "date": "1617210148",
    "name": "Henry",
    "status": "0"
  }
]

在 UPDATE 中使用它看起来像这样:

mysql> update example set content = json_set(content, concat(json_unquote(json_search(json_extract(content, '$[*].name'), 'one', 'Jack')), '.status'), '1');

还有很长的路要走。现在比较一下这有多难:

UPDATE content SET status = 1 WHERE name = 'Jack';

当您最终想要使用 SQL 表达式搜索或更新 JSON 文档中的各个字段时,将数据存储在 JSON 文档中是一个代价高昂的错误。它增加了您编写的任何代码的复杂性,并且在您转移到另一个项目后需要接管维护您的代码的开发人员会诅咒您的名字。

【讨论】:

  • 感谢比尔花时间帮助我。你的代码就像一个魅力,非常感谢!与简单行的 UPDATE 相比,我同意查询的复杂性。我将考虑您的意见,并将在虚拟表上运行一些压力测试以监控 2 个解决方案的性能。
  • @BillKarwin in update query (json_extract(content, '$[*].name'), 'one', 'Jack') --> 'ONE' 是什么意思?
  • 好问题!我会给你一个建议。这实际上是我学会使用这些 JSON 函数的方式。不需要很长时间。我相信它也会导致你的成功。我读了documentation for the JSON_SEARCH() function
猜你喜欢
  • 2021-09-19
  • 2015-03-05
  • 1970-01-01
  • 1970-01-01
  • 2018-01-14
  • 2021-04-14
  • 2021-09-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多