文档首页
MySQL 8.4 参考手册
相关文档 下载本手册
PDF (美国信纸) - 39.9Mb
PDF (A4) - 40.0Mb
手册页 (TGZ) - 258.5Kb
手册页 (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


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

13.5 JSON 数据类型

MySQL 支持由 RFC 7159 定义的本机 JSON 数据类型,它能够高效地访问 JSON (JavaScript 对象表示法) 文档中的数据。与将 JSON 格式的字符串存储在字符串列中相比,JSON 数据类型提供了以下优势

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

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

MySQL 8.4 还支持 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 8.4 中,优化器可以对 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 列值的 partial update 与将行的 partial update 写入二进制日志不同。可能将 JSON 列的完整更新记录为二进制日志中的 partial update。当最后两个条件中的一个(或两个)不满足,但其他条件满足时,就会发生这种情况。

另请参阅 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 值的函数”),以及通过将其他类型的值强制转换为 JSON 类型来获取 JSON 值,使用 CAST(value AS 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 文档中。假设在本例中,您想将一些包含代表关于 MySQL 的事实的字符串的 JSON 对象(每个对象都与相应的关键字配对)插入使用此处显示的 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 解析器实现。(Bug #86866,Bug #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] 不存在,因此相应的 value(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 8.4 不支持这些。

通配符 *** 令牌的使用方式如下。

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

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

  • [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}

    不相等的两个对象的顺序是不确定的,但确定性的。

  • STRING

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

    示例

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

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

    "A" < "a"
  • INTEGERDOUBLE

    JSON 值可以包含精确值数字和近似值数字。有关这些数字类型的常规讨论,请参见 第 11.1.2 节,“数值文字”

    第 14.3 节,“表达式求值中的类型转换” 中讨论了比较本机 MySQL 数值类型的规则,但比较 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 并产生警告。

ORDER BYGROUP BY 用于 JSON 值,其工作原理遵循这些原则。

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

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

  • 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 值转换为数字可能不会产生有意义的结果。