文档主页
MySQL 9.0 参考手册
相关文档 下载本手册
PDF (US Ltr) - 40.0Mb
PDF (A4) - 40.1Mb
手册页 (TGZ) - 258.2Kb
手册页 (Zip) - 365.3Kb
信息 (Gzip) - 4.0Mb
信息 (Zip) - 4.0Mb


MySQL 9.0 参考手册  /  数据类型  /  JSON 数据类型

13.5 JSON 数据类型

MySQL 支持由 RFC 7159 定义的原生 JSON 数据类型,该类型可以高效地访问 JSON(JavaScript 对象表示法)文档中的数据。与将 JSON 格式字符串存储在字符串列中相比,JSON 数据类型具有以下优势:

  • 自动验证存储在 JSON 列中的 JSON 文档。无效文档会产生错误。

  • 优化的存储格式。存储在 JSON 列中的 JSON 文档会被转换为内部格式,以便快速读取文档元素。当服务器稍后必须读取以这种二进制格式存储的 JSON 值时,无需从文本表示形式解析该值。二进制格式的结构使服务器能够直接通过键或数组索引查找子对象或嵌套值,而无需读取文档中它们之前或之后的所有值。

MySQL 9.0 还支持 RFC 7396 中定义的JSON 合并补丁格式,使用 JSON_MERGE_PATCH() 函数。有关示例和更多信息,请参阅此函数的说明以及JSON 值的规范化、合并和自动包装

注意

本文档使用等宽字体 JSON 特指 JSON 数据类型,使用常规字体“JSON”表示一般的 JSON 数据。

存储 JSON 文档所需的空间与 LONGBLOBLONGTEXT 大致相同;有关更多信息,请参阅第 13.7 节“数据类型存储需求”。重要的是要记住,存储在 JSON 列中的任何 JSON 文档的大小都受 max_allowed_packet 系统变量值的限制。(当服务器在内存中内部操作 JSON 值时,它可以大于此值;该限制在服务器存储它时适用。)可以使用 JSON_STORAGE_SIZE() 函数获取存储 JSON 文档所需的空间量;请注意,对于 JSON 列,存储大小(以及此函数返回的值)是该列在对其执行任何部分更新之前使用的存储大小(请参阅本节后面有关 JSON 部分更新优化的讨论)。

除了 JSON 数据类型之外,还有一组 SQL 函数可用于对 JSON 值执行操作,例如创建、操作和搜索。以下讨论显示了这些操作的示例。有关各个函数的详细信息,请参阅第 14.17 节“JSON 函数”

还有一组用于对 GeoJSON 值进行操作的空间函数。请参阅第 14.16.11 节“空间 GeoJSON 函数”

像其他二进制类型的列一样,JSON 列不能直接索引;但是,可以在生成的列上创建索引,该列从 JSON 列中提取标量值。有关详细示例,请参阅索引生成的列以提供 JSON 列索引

MySQL 优化器还会在与 JSON 表达式匹配的虚拟列上查找兼容的索引。

InnoDB 存储引擎支持 JSON 数组上的多值索引。请参阅多值索引

MySQL NDB 集群支持 JSON 列和 MySQL JSON 函数,包括在从 JSON 列生成的列上创建索引,以解决无法索引 JSON 列的问题。每个 NDB 表最多支持 3 个 JSON 列。

JSON 值的部分更新

在 MySQL 9.0 中,优化器可以对 JSON 列执行部分的、就地更新,而不是删除旧文档并将新文档完整写入该列。对于满足以下条件的更新,可以执行此优化:

  • 要更新的列被声明为 JSON

  • UPDATE 语句使用三个函数 JSON_SET()JSON_REPLACE()JSON_REMOVE() 中的任何一个来更新该列。不能将列值的直接赋值(例如,UPDATE mytable SET jcol = '{"a": 10, "b": 25}')作为部分更新执行。

    可以以这种方式优化单个 UPDATE 语句中多个 JSON 列的更新;MySQL 只能对使用上面列出的三个函数更新其值的列执行部分更新。

  • 输入列和目标列必须是同一列;诸如 UPDATE mytable SET jcol1 = JSON_SET(jcol2, '$.a', 100) 之类的语句不能作为部分更新执行。

    只要输入列和目标列相同,更新就可以使用对前一项中列出的任何函数的嵌套调用,以任何组合进行。

  • 所有更改都使用新值替换现有的数组或对象值,并且不会向父对象或数组添加任何新元素。

  • 被替换的值必须至少与替换值一样大。换句话说,新值不能大于旧值。

    当先前的部分更新为较大的值留下了足够的空间时,可能会出现此要求的例外情况。可以使用函数 JSON_STORAGE_FREE() 查看 JSON 列的任何部分更新释放了多少空间。

此类部分更新可以使用节省空间的紧凑格式写入二进制日志;可以通过将 binlog_row_value_options 系统变量设置为 PARTIAL_JSON 来启用此功能。

区分对存储在表中的 JSON 列值的局部更新与将行的局部更新写入二进制日志非常重要。 JSON 列的完整更新可能会在二进制日志中记录为局部更新。当上一个列表中的最后两个条件中的一个(或两个)不满足但其他条件满足时,就会发生这种情况。

另请参阅 binlog_row_value_options 的说明。

接下来的几节将提供有关创建和操作 JSON 值的基本信息。

创建 JSON 值

JSON 数组包含一个值列表,这些值用逗号分隔并包含在 [] 字符内

["abc", 10, null, true, false]

JSON 对象包含一组键值对,这些键值对用逗号分隔并包含在 {} 字符内

{"k1": "value", "k2": 10}

如示例所示,JSON 数组和对象可以包含标量值,这些标量值可以是字符串或数字、JSON 空值字面量或 JSON 布尔值 true 或 false 字面量。 JSON 对象中的键必须是字符串。也允许使用时间(日期、时间或日期时间)标量值

["12:18:29.000000", "2015-07-29", "2015-07-29 12:18:29.000000"]

JSON 数组元素和 JSON 对象键值内允许嵌套

[99, {"id": "HK500", "cost": 75.99}, ["hot", "cold"]]
{"k1": "value", "k2": [10, 20]}

您还可以从 MySQL 为此目的提供的许多函数中获取 JSON 值(请参阅 第 14.17.2 节“创建 JSON 值的函数”)以及通过使用 CAST(value AS JSON) 将其他类型的值转换为 JSON 类型(请参阅 在 JSON 和非 JSON 值之间转换)。接下来的几段描述了 MySQL 如何处理作为输入提供的 JSON 值。

在 MySQL 中,JSON 值以字符串形式写入。 MySQL 会解析在需要 JSON 值的上下文中使用的任何字符串,如果它作为 JSON 无效,则会产生错误。这些上下文包括将值插入具有 JSON 数据类型的列中,以及将参数传递给需要 JSON 值的函数(在 MySQL JSON 函数的文档中通常显示为 json_docjson_val),如下例所示

  • 如果该值是有效的 JSON 值,则尝试将值插入 JSON 列会成功,否则会失败

    mysql> CREATE TABLE t1 (jdoc JSON);
    Query OK, 0 rows affected (0.20 sec)
    
    mysql> INSERT INTO t1 VALUES('{"key1": "value1", "key2": "value2"}');
    Query OK, 1 row affected (0.01 sec)
    
    mysql> INSERT INTO t1 VALUES('[1, 2,');
    ERROR 3140 (22032) at line 2: Invalid JSON text:
    "Invalid value." at position 6 in value (or column) '[1, 2,'.

    此类错误消息中 在位置 N 的位置是从 0 开始的,但应将其视为值中实际出现问题的粗略指示。

  • JSON_TYPE() 函数需要一个 JSON 参数,并尝试将其解析为 JSON 值。如果该值有效,则返回该值的 JSON 类型,否则会产生错误

    mysql> SELECT JSON_TYPE('["a", "b", 1]');
    +----------------------------+
    | JSON_TYPE('["a", "b", 1]') |
    +----------------------------+
    | ARRAY                      |
    +----------------------------+
    
    mysql> SELECT JSON_TYPE('"hello"');
    +----------------------+
    | JSON_TYPE('"hello"') |
    +----------------------+
    | STRING               |
    +----------------------+
    
    mysql> SELECT JSON_TYPE('hello');
    ERROR 3146 (22032): Invalid data type for JSON data in argument 1
    to function json_type; a JSON string or JSON type is required.

MySQL 使用 utf8mb4 字符集和 utf8mb4_bin 校对规则来处理 JSON 上下文中使用的字符串。其他字符集中的字符串将根据需要转换为 utf8mb4。(对于 asciiutf8mb3 字符集中的字符串,不需要转换,因为 asciiutf8mb3utf8mb4 的子集。)

作为使用文字字符串编写 JSON 值的替代方法,可以使用函数从组件元素组合 JSON 值。 JSON_ARRAY() 接受一个(可能为空)的值列表,并返回一个包含这些值的 JSON 数组

mysql> SELECT JSON_ARRAY('a', 1, NOW());
+----------------------------------------+
| JSON_ARRAY('a', 1, NOW())              |
+----------------------------------------+
| ["a", 1, "2015-07-27 09:43:47.000000"] |
+----------------------------------------+

JSON_OBJECT() 接受一个(可能为空)的键值对列表,并返回一个包含这些对的 JSON 对象

mysql> SELECT JSON_OBJECT('key1', 1, 'key2', 'abc');
+---------------------------------------+
| JSON_OBJECT('key1', 1, 'key2', 'abc') |
+---------------------------------------+
| {"key1": 1, "key2": "abc"}            |
+---------------------------------------+

JSON_MERGE_PRESERVE() 接受两个或多个 JSON 文档,并返回合并的结果

mysql> SELECT JSON_MERGE_PRESERVE('["a", 1]', '{"key": "value"}');
+-----------------------------------------------------+
| JSON_MERGE_PRESERVE('["a", 1]', '{"key": "value"}') |
+-----------------------------------------------------+
| ["a", 1, {"key": "value"}]                          |
+-----------------------------------------------------+
1 row in set (0.00 sec)

有关合并规则的信息,请参阅 JSON 值的规范化、合并和自动包装

(MySQL 还支持 JSON_MERGE_PATCH(),它的行为略有不同。有关这两个函数之间区别的信息,请参阅 JSON_MERGE_PATCH() 与 JSON_MERGE_PRESERVE() 的比较。)

可以将 JSON 值分配给用户定义的变量

mysql> SET @j = JSON_OBJECT('key', 'value');
mysql> SELECT @j;
+------------------+
| @j               |
+------------------+
| {"key": "value"} |
+------------------+

但是,用户定义的变量不能是 JSON 数据类型,因此,尽管前面示例中的 @j 看起来像一个 JSON 值,并且具有与 JSON 值相同的字符集和排序规则,但它没有 JSON 数据类型。相反,JSON_OBJECT() 的结果在分配给变量时会转换为字符串。

通过转换 JSON 值生成的字符串的字符集为 utf8mb4,排序规则为 utf8mb4_bin

mysql> SELECT CHARSET(@j), COLLATION(@j);
+-------------+---------------+
| CHARSET(@j) | COLLATION(@j) |
+-------------+---------------+
| utf8mb4     | utf8mb4_bin   |
+-------------+---------------+

因为 utf8mb4_bin 是二进制排序规则,所以 JSON 值的比较区分大小写。

mysql> SELECT JSON_ARRAY('x') = JSON_ARRAY('X');
+-----------------------------------+
| JSON_ARRAY('x') = JSON_ARRAY('X') |
+-----------------------------------+
|                                 0 |
+-----------------------------------+

区分大小写也适用于 JSON nulltruefalse 字面量,它们必须始终以小写形式编写

mysql> SELECT JSON_VALID('null'), JSON_VALID('Null'), JSON_VALID('NULL');
+--------------------+--------------------+--------------------+
| JSON_VALID('null') | JSON_VALID('Null') | JSON_VALID('NULL') |
+--------------------+--------------------+--------------------+
|                  1 |                  0 |                  0 |
+--------------------+--------------------+--------------------+

mysql> SELECT CAST('null' AS JSON);
+----------------------+
| CAST('null' AS JSON) |
+----------------------+
| null                 |
+----------------------+
1 row in set (0.00 sec)

mysql> SELECT CAST('NULL' AS JSON);
ERROR 3141 (22032): Invalid JSON text in argument 1 to function cast_as_json:
"Invalid value." at position 0 in 'NULL'.

JSON 字面量的区分大小写与 SQL NULLTRUEFALSE 字面量的区分大小写不同,后者可以使用任何字母大小写

mysql> SELECT ISNULL(null), ISNULL(Null), ISNULL(NULL);
+--------------+--------------+--------------+
| ISNULL(null) | ISNULL(Null) | ISNULL(NULL) |
+--------------+--------------+--------------+
|            1 |            1 |            1 |
+--------------+--------------+--------------+

有时可能需要或希望将引号字符("')插入 JSON 文档中。假设在本例中,您要插入一些 JSON 对象,这些对象包含表示关于 MySQL 的一些事实的句子字符串,每个句子都与一个适当的关键字配对,并插入到使用此处显示的 SQL 语句创建的表中

mysql> CREATE TABLE facts (sentence JSON);

在这些关键字-句子对中,有这样一对

mascot: The MySQL mascot is a dolphin named "Sakila".

将此作为 JSON 对象插入 facts 表的一种方法是使用 MySQL JSON_OBJECT() 函数。在这种情况下,您必须使用反斜杠转义每个引号字符,如下所示

mysql> INSERT INTO facts VALUES
     >   (JSON_OBJECT("mascot", "Our mascot is a dolphin named \"Sakila\"."));

如果您将该值作为 JSON 对象字面量插入,则此方法不起作用,在这种情况下,您必须使用双反斜杠转义序列,如下所示

mysql> INSERT INTO facts VALUES
     >   ('{"mascot": "Our mascot is a dolphin named \\"Sakila\\"."}');

使用双反斜杠可以防止 MySQL 执行转义序列处理,而是使其将字符串字面量传递给存储引擎进行处理。在以刚才显示的任一方式插入 JSON 对象后,您可以通过执行简单的 SELECT 来查看 JSON 列值中是否存在反斜杠,如下所示

mysql> SELECT sentence FROM facts;
+---------------------------------------------------------+
| sentence                                                |
+---------------------------------------------------------+
| {"mascot": "Our mascot is a dolphin named \"Sakila\"."} |
+---------------------------------------------------------+

要使用 mascot 作为键查找此特定句子,可以使用列路径运算符 ->,如下所示

mysql> SELECT col->"$.mascot" FROM qtest;
+---------------------------------------------+
| col->"$.mascot"                             |
+---------------------------------------------+
| "Our mascot is a dolphin named \"Sakila\"." |
+---------------------------------------------+
1 row in set (0.00 sec)

这将保留反斜杠以及周围的引号。要使用 mascot 作为键显示所需的值,但不包括周围的引号或任何转义字符,请使用内联路径运算符 ->>,如下所示

mysql> SELECT sentence->>"$.mascot" FROM facts;
+-----------------------------------------+
| sentence->>"$.mascot"                   |
+-----------------------------------------+
| Our mascot is a dolphin named "Sakila". |
+-----------------------------------------+
注意

如果启用了 NO_BACKSLASH_ESCAPES 服务器 SQL 模式,则前面的示例将不起作用。如果设置了此模式,则可以使用单个反斜杠而不是双反斜杠来插入 JSON 对象字面量,并且反斜杠将被保留。如果在执行插入时使用 JSON_OBJECT() 函数并且设置了此模式,则必须交替使用单引号和双引号,如下所示

mysql> INSERT INTO facts VALUES
     > (JSON_OBJECT('mascot', 'Our mascot is a dolphin named "Sakila".'));

有关此模式对 JSON 值中转义字符的影响的更多信息,请参阅 JSON_UNQUOTE() 函数的说明。

JSON 值的规范化、合并和自动包装

当解析一个字符串并发现它是一个有效的 JSON 文档时,也会对其进行规范化。这意味着将丢弃键与从左到右读取的文档中稍后找到的键重复的成员。以下 JSON_OBJECT() 调用生成的对象值仅包含第二个 key1 元素,因为该键名在值中出现得较早,如下所示

mysql> SELECT JSON_OBJECT('key1', 1, 'key2', 'abc', 'key1', 'def');
+------------------------------------------------------+
| JSON_OBJECT('key1', 1, 'key2', 'abc', 'key1', 'def') |
+------------------------------------------------------+
| {"key1": "def", "key2": "abc"}                       |
+------------------------------------------------------+

将值插入 JSON 列时也会执行规范化,如下所示

mysql> CREATE TABLE t1 (c1 JSON);

mysql> INSERT INTO t1 VALUES
     >     ('{"x": 17, "x": "red"}'),
     >     ('{"x": 17, "x": "red", "x": [3, 5, 7]}');

mysql> SELECT c1 FROM t1;
+------------------+
| c1               |
+------------------+
| {"x": "red"}     |
| {"x": [3, 5, 7]} |
+------------------+

这种 最后重复的键获胜 行为由 RFC 7159 建议,并且由大多数 JavaScript 解析器实现。(错误 #86866,错误 #26369555)

MySQL 会丢弃原始 JSON 文档中键、值或元素之间的额外空格,并在显示时在每个逗号(,)或冒号(:)后面保留(或在必要时插入)一个空格。这样做是为了增强可读性。

生成 JSON 值的 MySQL 函数(请参阅 第 14.17.2 节“创建 JSON 值的函数”)始终返回规范化值。

为了提高查找效率,MySQL 还会对 JSON 对象的键进行排序。 您应该注意,此排序的结果可能会发生变化,并且不能保证在不同版本之间保持一致

合并 JSON 值

支持两种合并算法,由函数 JSON_MERGE_PRESERVE()JSON_MERGE_PATCH() 实现。它们在处理重复键的方式上有所不同: JSON_MERGE_PRESERVE() 保留重复键的值,而 JSON_MERGE_PATCH() 丢弃除最后一个值之外的所有值。接下来的几段解释了这两个函数如何处理不同 JSON 文档组合(即对象和数组)的合并。

合并数组。 在组合多个数组的上下文中,这些数组将合并为一个数组。 JSON_MERGE_PRESERVE() 通过将稍后命名的数组连接到第一个数组的末尾来实现这一点。 JSON_MERGE_PATCH() 将每个参数视为一个由单个元素组成的数组(因此其索引为 0),然后应用 最后重复的键获胜 逻辑来仅选择最后一个参数。您可以比较此查询显示的结果

mysql> SELECT
    ->   JSON_MERGE_PRESERVE('[1, 2]', '["a", "b", "c"]', '[true, false]') AS Preserve,
    ->   JSON_MERGE_PATCH('[1, 2]', '["a", "b", "c"]', '[true, false]') AS Patch\G
*************************** 1. row ***************************
Preserve: [1, 2, "a", "b", "c", true, false]
   Patch: [true, false]

合并多个对象时会生成一个对象。 JSON_MERGE_PRESERVE() 通过将该键的所有唯一值组合在一个数组中来处理具有相同键的多个对象;然后,此数组将用作结果中该键的值。 JSON_MERGE_PATCH() 从左到右丢弃找到重复键的值,以便结果仅包含该键的最后一个值。以下查询说明了重复键 a 的结果差异

mysql> SELECT
    ->   JSON_MERGE_PRESERVE('{"a": 1, "b": 2}', '{"c": 3, "a": 4}', '{"c": 5, "d": 3}') AS Preserve,
    ->   JSON_MERGE_PATCH('{"a": 3, "b": 2}', '{"c": 3, "a": 4}', '{"c": 5, "d": 3}') AS Patch\G
*************************** 1. row ***************************
Preserve: {"a": [1, 4], "b": 2, "c": [3, 5], "d": 3}
   Patch: {"a": 4, "b": 2, "c": 5, "d": 3}

在需要数组值的上下文中使用的非数组值将自动包装:该值将用 [] 字符括起来,以将其转换为数组。在以下语句中,每个参数都自动包装为一个数组([1][2])。然后将它们合并以生成一个结果数组;与前两种情况一样,JSON_MERGE_PRESERVE() 组合具有相同键的值,而 JSON_MERGE_PATCH() 丢弃除最后一个重复键之外的所有值,如下所示

mysql> SELECT
	  ->   JSON_MERGE_PRESERVE('1', '2') AS Preserve,
	  ->   JSON_MERGE_PATCH('1', '2') AS Patch\G
*************************** 1. row ***************************
Preserve: [1, 2]
   Patch: 2

数组和对象值通过将对象自动包装为数组并通过组合值或根据合并函数的选择(分别为 JSON_MERGE_PRESERVE()JSON_MERGE_PATCH())按“最后一个重复键获胜 合并数组来合并,如本例所示

mysql> SELECT
	  ->   JSON_MERGE_PRESERVE('[10, 20]', '{"a": "x", "b": "y"}') AS Preserve,
	  ->   JSON_MERGE_PATCH('[10, 20]', '{"a": "x", "b": "y"}') AS Patch\G
*************************** 1. row ***************************
Preserve: [10, 20, {"a": "x", "b": "y"}]
   Patch: {"a": "x", "b": "y"}

搜索和修改 JSON 值

JSON 路径表达式选择 JSON 文档中的值。

路径表达式对提取部分或修改 JSON 文档的函数很有用,用于指定在该文档中的哪个位置进行操作。例如,以下查询从 JSON 文档中提取具有 name 键的成员的值

mysql> SELECT JSON_EXTRACT('{"id": 14, "name": "Aztalan"}', '$.name');
+---------------------------------------------------------+
| JSON_EXTRACT('{"id": 14, "name": "Aztalan"}', '$.name') |
+---------------------------------------------------------+
| "Aztalan"                                               |
+---------------------------------------------------------+

路径语法使用前导 $ 字符表示正在考虑的 JSON 文档,可选后跟选择器,这些选择器指示文档中越来越具体的部分

  • 句点后跟键名表示具有给定键的对象中的成员。如果名称不带引号在路径表达式中不合法(例如,如果它包含空格),则必须在双引号中指定键名。

  • [N] 附加到选择数组的 path 表示数组中位置 N 处的值。数组位置是从零开始的整数。如果 path 未选择数组值,则 path[0] 的计算结果与 path 相同

    mysql> SELECT JSON_SET('"x"', '$[0]', 'a');
    +------------------------------+
    | JSON_SET('"x"', '$[0]', 'a') |
    +------------------------------+
    | "a"                          |
    +------------------------------+
    1 row in set (0.00 sec)
  • [M to N] 指定从位置 M 处的值开始到位置 N 处的值结束的数组值的子集或范围。

    last 被支持作为最右边的数组元素的索引的同义词。也支持数组元素的相对寻址。如果 path 未选择数组值,则 path[last] 的计算结果与 path 相同,如本节后面所示(请参阅 最右边的数组元素)。

  • 路径可以包含 *** 通配符

    • .[*] 计算 JSON 对象中所有成员的值。

    • [*] 计算 JSON 数组中所有元素的值。

    • prefix**suffix 计算以命名前缀开头并以命名后缀结尾的所有路径。

  • 文档中不存在的路径(计算结果为不存在的数据)计算结果为 NULL

$ 指的是这个有三个元素的 JSON 数组

[3, {"a": [5, 6], "b": 10}, [99, 100]]

然后

  • $[0] 计算结果为 3

  • $[1] 计算结果为 {"a": [5, 6], "b": 10}

  • $[2] 计算结果为 [99, 100]

  • $[3] 计算结果为 NULL(它指的是不存在的第四个数组元素)。

因为 $[1]$[2] 计算结果为非标量值,所以它们可以用作选择嵌套值的更具体路径表达式的基础。例子

  • $[1].a 计算结果为 [5, 6]

  • $[1].a[1] 计算结果为 6

  • $[1].b 计算结果为 10

  • $[2][0] 计算结果为 99

如前所述,如果未加引号的键名在路径表达式中不合法,则命名键的路径组件必须加引号。让 $ 指的是这个值

{"a fish": "shark", "a bird": "sparrow"}

这两个键都包含空格,必须加引号

  • $."a fish" 计算结果为 shark

  • $."a bird" 计算结果为 sparrow

使用通配符的路径计算结果为一个数组,该数组可以包含多个值

mysql> SELECT JSON_EXTRACT('{"a": 1, "b": 2, "c": [3, 4, 5]}', '$.*');
+---------------------------------------------------------+
| JSON_EXTRACT('{"a": 1, "b": 2, "c": [3, 4, 5]}', '$.*') |
+---------------------------------------------------------+
| [1, 2, [3, 4, 5]]                                       |
+---------------------------------------------------------+
mysql> SELECT JSON_EXTRACT('{"a": 1, "b": 2, "c": [3, 4, 5]}', '$.c[*]');
+------------------------------------------------------------+
| JSON_EXTRACT('{"a": 1, "b": 2, "c": [3, 4, 5]}', '$.c[*]') |
+------------------------------------------------------------+
| [3, 4, 5]                                                  |
+------------------------------------------------------------+

在以下示例中,路径 $**.b 计算结果为多个路径($.a.b$.c.b),并生成匹配路径值的数组

mysql> SELECT JSON_EXTRACT('{"a": {"b": 1}, "c": {"b": 2}}', '$**.b');
+---------------------------------------------------------+
| JSON_EXTRACT('{"a": {"b": 1}, "c": {"b": 2}}', '$**.b') |
+---------------------------------------------------------+
| [1, 2]                                                  |
+---------------------------------------------------------+

来自 JSON 数组的范围。  您可以将范围与 to 关键字一起使用来指定 JSON 数组的子集。例如,$[1 to 3] 包含数组的第二个、第三个和第四个元素,如下所示

mysql> SELECT JSON_EXTRACT('[1, 2, 3, 4, 5]', '$[1 to 3]');
+----------------------------------------------+
| JSON_EXTRACT('[1, 2, 3, 4, 5]', '$[1 to 3]') |
+----------------------------------------------+
| [2, 3, 4]                                    |
+----------------------------------------------+
1 row in set (0.00 sec)

语法是 M to N,其中 MN 分别是 JSON 数组中元素范围的第一个和最后一个索引。N 必须大于 MM 必须大于或等于 0。数组元素从 0 开始索引。

您可以在支持通配符的上下文中使用范围。

最右边的数组元素。  last 关键字被支持作为数组中最后一个元素的索引的同义词。像这样,last - N 形式的表达式可用于相对寻址以及范围定义中

mysql> SELECT JSON_EXTRACT('[1, 2, 3, 4, 5]', '$[last-3 to last-1]');
+--------------------------------------------------------+
| JSON_EXTRACT('[1, 2, 3, 4, 5]', '$[last-3 to last-1]') |
+--------------------------------------------------------+
| [2, 3, 4]                                              |
+--------------------------------------------------------+
1 row in set (0.01 sec)

如果针对非数组值计算路径,则计算结果与将值包装在单元素数组中相同

mysql> SELECT JSON_REPLACE('"Sakila"', '$[last]', 10);
+-----------------------------------------+
| JSON_REPLACE('"Sakila"', '$[last]', 10) |
+-----------------------------------------+
| 10                                      |
+-----------------------------------------+
1 row in set (0.00 sec)

您可以将 column->path 与 JSON 列标识符和 JSON 路径表达式一起用作 JSON_EXTRACT(column, path) 的同义词。有关更多信息,请参阅 第 14.17.3 节“搜索 JSON 值的函数”。另请参阅 索引生成的列以提供 JSON 列索引

有些函数采用现有的 JSON 文档,以某种方式对其进行修改,并返回生成的修改后的文档。路径表达式指示在文档中的哪个位置进行更改。例如,JSON_SET()JSON_INSERT()JSON_REPLACE() 函数都采用一个 JSON 文档,以及一个或多个路径值对,这些路径值对描述了在何处修改文档以及要使用的值。这些函数在处理文档中现有值和不存在值的方式上有所不同。

考虑此文档

mysql> SET @j = '["a", {"b": [true, false]}, [10, 20]]';

JSON_SET() 替换现有路径的值,并为不存在的路径添加值:。

mysql> SELECT JSON_SET(@j, '$[1].b[0]', 1, '$[2][2]', 2);
+--------------------------------------------+
| JSON_SET(@j, '$[1].b[0]', 1, '$[2][2]', 2) |
+--------------------------------------------+
| ["a", {"b": [1, false]}, [10, 20, 2]]      |
+--------------------------------------------+

在这种情况下,路径 $[1].b[0] 选择一个现有值(true),该值将替换为路径参数(1)后面的值。路径 $[2][2] 不存在,因此相应的值(2)将添加到 $[2] 选择的值中。

JSON_INSERT() 添加新值,但不替换现有值

mysql> SELECT JSON_INSERT(@j, '$[1].b[0]', 1, '$[2][2]', 2);
+-----------------------------------------------+
| JSON_INSERT(@j, '$[1].b[0]', 1, '$[2][2]', 2) |
+-----------------------------------------------+
| ["a", {"b": [true, false]}, [10, 20, 2]]      |
+-----------------------------------------------+

JSON_REPLACE() 替换现有值并忽略新值

mysql> SELECT JSON_REPLACE(@j, '$[1].b[0]', 1, '$[2][2]', 2);
+------------------------------------------------+
| JSON_REPLACE(@j, '$[1].b[0]', 1, '$[2][2]', 2) |
+------------------------------------------------+
| ["a", {"b": [1, false]}, [10, 20]]             |
+------------------------------------------------+

路径值对从左到右计算。计算一对生成的新文档将成为计算下一对的新值。

JSON_REMOVE() 采用一个 JSON 文档和一个或多个路径,这些路径指定要从文档中删除的值。返回值是原始文档减去文档中存在的路径选择的值

mysql> SELECT JSON_REMOVE(@j, '$[2]', '$[1].b[1]', '$[1].b[1]');
+---------------------------------------------------+
| JSON_REMOVE(@j, '$[2]', '$[1].b[1]', '$[1].b[1]') |
+---------------------------------------------------+
| ["a", {"b": [true]}]                              |
+---------------------------------------------------+

这些路径具有以下效果

  • $[2] 匹配 [10, 20] 并将其删除。

  • $[1].b[1] 的第一个实例与 b 元素中的 false 匹配并将其删除。

  • $[1].b[1] 的第二个实例不匹配任何内容:该元素已被删除,路径不再存在,并且没有任何效果。

JSON 路径语法

MySQL 支持并在本手册的其他地方描述的许多 JSON 函数(请参阅 第 14.17 节“JSON 函数”)都需要路径表达式来标识 JSON 文档中的特定元素。路径由路径的范围后跟一个或多个路径段组成。对于 MySQL JSON 函数中使用的路径,范围始终是被搜索或以其他方式操作的文档,由前导 $ 字符表示。路径段用句点字符 (.) 分隔。数组中的单元格用 [N] 表示,其中 N 是一个非负整数。键名必须是用双引号引起来的字符串或有效的 ECMAScript 标识符(请参阅 ECMAScript 语言规范 中的 标识符名称和标识符)。路径表达式(如 JSON 文本)应使用 asciiutf8mb3utf8mb4 字符集进行编码。其他字符编码将隐式强制转换为 utf8mb4。完整的语法如下所示

pathExpression:
    scope[(pathLeg)*]

pathLeg:
    member | arrayLocation | doubleAsterisk

member:
    period ( keyName | asterisk )

arrayLocation:
    leftBracket ( nonNegativeInteger | asterisk ) rightBracket

keyName:
    ESIdentifier | doubleQuotedString

doubleAsterisk:
    '**'

period:
    '.'

asterisk:
    '*'

leftBracket:
    '['

rightBracket:
    ']'

如前所述,在 MySQL 中,路径的范围始终是被操作的文档,表示为 $。您可以在 JSON 路径表达式中使用 '$' 作为文档的同义词。

注意

某些实现支持 JSON 路径范围的列引用;MySQL 9.0 不支持这些。

通配符 *** 标记的使用方法如下

  • .* 表示对象中所有成员的值。

  • [*] 表示数组中所有单元格的值。

  • [prefix]**suffix 表示以 prefix 开头并以 suffix 结尾的所有路径。prefix 是可选的,而 suffix 是必需的;换句话说,路径不能以 ** 结尾。

    此外,路径不能包含序列 ***

有关路径语法示例,请参阅将路径作为参数的各种 JSON 函数的描述,例如 JSON_CONTAINS_PATH()JSON_SET()JSON_REPLACE()。有关包含使用 *** 通配符的示例,请参阅 JSON_SEARCH() 函数的描述。

MySQL 还支持使用 to 关键字(例如 $[2 to 10])表示 JSON 数组子集的范围表示法,以及将 last 关键字作为数组最右边元素的同义词。有关更多信息和示例,请参阅 搜索和修改 JSON 值

JSON 值的比较和排序

可以使用 =<<=>>=<>!=<=> 运算符比较 JSON 值。

以下比较运算符和函数尚不支持 JSON 值

对于刚才列出的比较运算符和函数,一种解决方法是将 JSON 值转换为原生 MySQL 数值或字符串数据类型,以便它们具有一致的非 JSON 标量类型。

JSON 值的比较发生在两个级别。第一级比较基于所比较值的 JSON 类型。如果类型不同,则比较结果仅由哪个类型具有更高的优先级决定。如果两个值具有相同的 JSON 类型,则使用特定于类型的规则进行第二级比较。

以下列表显示了 JSON 类型的优先级,从最高优先级到最低优先级。(类型名称是由 JSON_TYPE() 函数返回的名称。)同一行上显示的类型具有相同的优先级。列表中较早列出的 JSON 类型的任何值都比列表中较晚列出的 JSON 类型的任何值大。

BLOB
BIT
OPAQUE
DATETIME
TIME
DATE
BOOLEAN
ARRAY
OBJECT
STRING
INTEGER, DOUBLE
NULL

对于具有相同优先级的 JSON 值,比较规则是特定于类型的

  • BLOB

    比较两个值的前 N 个字节,其中 N 是较短值的字节数。如果两个值的前 N 个字节相同,则较短的值排在较长值的前面。

  • BIT

    规则与 BLOB 相同。

  • OPAQUE

    规则与 BLOB 相同。OPAQUE 值是未归类为其他任何类型的的值。

  • DATETIME

    表示较早时间点的值排在表示较晚时间点的值的前面。如果两个值分别来自 MySQL DATETIMETIMESTAMP 类型,则如果它们表示相同的时间点,则它们相等。

  • TIME

    两个时间值中较小的一个排在较大的一个前面。

  • DATE

    较早的日期排在较晚的日期前面。

  • ARRAY

    如果两个 JSON 数组的长度相同,并且数组中对应位置的值相等,则这两个数组相等。

    如果数组不相等,则它们的顺序由第一个存在差异的位置的元素确定。在该位置具有较小值的数组排在前面。如果较短数组的所有值都等于较长数组中的对应值,则较短数组排在前面。

    例如

    [] < ["a"] < ["ab"] < ["ab", "cd", "ef"] < ["ab", "ef"]
  • BOOLEAN

    JSON false 字面量小于 JSON true 字面量。

  • OBJECT

    如果两个 JSON 对象具有相同的键集,并且每个键在两个对象中具有相同的值,则这两个对象相等。

    例如

    {"a": 1, "b": 2} = {"b": 2, "a": 1}

    两个不相等的 JSON 对象的顺序未指定,但确定。

  • STRING

    字符串按要比较的两个字符串的 utf8mb4 表示形式的前 N 个字节进行字典顺序排序,其中 N 是较短字符串的长度。如果两个字符串的前 N 个字节相同,则较短的字符串被认为小于较长的字符串。

    例如

    "a" < "ab" < "b" < "bc"

    此排序等效于使用排序规则 utf8mb4_bin 对 SQL 字符串进行排序。因为 utf8mb4_bin 是二进制排序规则,所以 JSON 值的比较区分大小写

    "A" < "a"
  • INTEGERDOUBLE

    JSON 值可以包含精确值数字和近似值数字。有关这些类型数字的一般讨论,请参阅第 11.1.2 节“数值字面量”

    比较原生 MySQL 数值类型的规则在第 14.3 节“表达式求值中的类型转换”中讨论,但比较 JSON 值中的数字的规则略有不同

    • 在分别使用原生 MySQL INTDOUBLE 数值类型的两列之间进行比较时,已知所有比较都涉及一个整数和一个双精度数,因此对于所有行,整数都将转换为双精度数。也就是说,精确值数字将转换为近似值数字。

    • 另一方面,如果查询比较包含数字的两列 JSON,则无法事先知道数字是整数还是双精度数。为了在所有行中提供最一致的行为,MySQL 会将近似值数字转换为精确值数字。结果排序是一致的,并且不会丢失精确值数字的精度。例如,给定标量 9223372036854775805、9223372036854775806、9223372036854775807 和 9.223372036854776e18,顺序如下:

      9223372036854775805 < 9223372036854775806 < 9223372036854775807
      < 9.223372036854776e18 = 9223372036854776000 < 9223372036854776001

    如果 JSON 比较使用非 JSON 数值比较规则,则可能会出现不一致的排序。数字的常见 MySQL 比较规则会产生以下顺序

    • 整数比较

      9223372036854775805 < 9223372036854775806 < 9223372036854775807

      (未定义 9.223372036854776e18)

    • 双精度比较

      9223372036854775805 = 9223372036854775806 = 9223372036854775807 = 9.223372036854776e18

对于任何 JSON 值与 SQL NULL 的比较,结果为 UNKNOWN

对于 JSON 值和非 JSON 值的比较,非 JSON 值将根据下表中的规则转换为 JSON,然后按照前面所述比较这些值。

在 JSON 和非 JSON 值之间转换

下表总结了 MySQL 在 JSON 值和其他类型的值之间进行强制转换时遵循的规则

表 13.3 JSON 转换规则

其他类型 CAST(其他类型 AS JSON) CAST(JSON AS 其他类型)
JSON 无变化 无变化
utf8 字符类型(utf8mb4utf8mb3ascii 该字符串被解析为 JSON 值。 JSON 值被序列化为 utf8mb4 字符串。
其他字符类型 其他字符编码将隐式转换为 utf8mb4,并按照此字符类型的说明进行处理。 JSON 值被序列化为 utf8mb4 字符串,然后强制转换为其他字符编码。结果可能没有意义。
NULL 结果为 JSON 类型的 NULL 值。 不适用。
几何类型 通过调用 ST_AsGeoJSON() 将几何值转换为 JSON 文档。 非法操作。解决方法:将 CAST(json_val AS CHAR) 的结果传递给 ST_GeomFromGeoJSON()
所有其他类型 结果为由单个标量值组成的 JSON 文档。 如果 JSON 文档由目标类型的单个标量值组成,并且该标量值可以强制转换为目标类型,则成功。否则,返回 NULL 并生成警告。

JSON 值的 ORDER BYGROUP BY 根据以下原则工作

  • 标量 JSON 值的排序使用与前面讨论中相同的规则。

  • 对于升序排序,SQL NULL 排在所有 JSON 值(包括 JSON 空字面量)之前;对于降序排序,SQL NULL 排在所有 JSON 值(包括 JSON 空字面量)之后。

  • JSON 值的排序键受 max_sort_length 系统变量的值限制,因此仅在前 max_sort_length 个字节之后才不同的键将比较为相等。

  • 当前不支持非标量值的排序,并且会发出警告。

对于排序,将 JSON 标量转换为其他一些原生 MySQL 类型可能会有所帮助。例如,如果名为 jdoc 的列包含 JSON 对象,这些对象的成员由 id 键和一个非负值组成,请使用以下表达式按 id 值排序

ORDER BY CAST(JSON_EXTRACT(jdoc, '$.id') AS UNSIGNED)

如果碰巧定义了一个生成的列以使用与 ORDER BY 中相同的表达式,则 MySQL 优化器会识别该列并考虑使用该索引作为查询执行计划。请参阅第 10.3.11 节“优化器对生成列索引的使用”

JSON 值的聚合

对于 JSON 值的聚合,SQL NULL 值将被忽略,就像其他数据类型一样。非 NULL 值将转换为数值类型并进行聚合,但 MIN()MAX()GROUP_CONCAT() 除外。对于作为数值标量的 JSON 值,转换为数字应会产生有意义的结果,尽管(取决于值)可能会发生截断和精度损失。其他 JSON 值转换为数字可能不会产生有意义的结果。