13.5 JSON 数据类型
MySQL 支持一个 native 的 JSON
数据类型,定义于RFC 7159,使得对 JSON 文档中的数据进行高效访问。JSON
数据类型提供了以下优点,可以将 JSON 格式字符串存储在 string 列中:
-
自动验证存储在
JSON
列中的 JSON 文档。如果文档无效,会产生错误。 -
优化存储格式。JSON 文档在
JSON
列中被转换为一个内部格式,这样可以快速访问文档元素。当服务器后续需要从这个二进制格式中读取 JSON 值时,值不需要从文本表示中解析。二进制格式结构化,以便服务器可以直接通过键或数组索引来查找子对象或嵌套值,而无需先读取所有值或在它们之前或之后。
MySQL 8.4 还支持根据 RFC 7396 定义的 JSON Merge Patch 格式,使用 JSON_MERGE_PATCH()
函数。请查看该函数的描述,以及Normalization, Merging, and Autowrapping of JSON Values,以获取示例和进一步信息。
本讨论使用 JSON
表示特定 JSON 数据类型,并使用“JSON” 表示一般的 JSON 数据。
存储一个JSON
文档所需的空间大致与LONGBLOB
或LONGTEXT
相似;请参阅第13.7节,“数据类型存储要求”,了解更多信息。需要注意的是,任何在JSON
列中存储的JSON文档的大小都受max_allowed_packet
系统变量的值限制。 (当服务器在内存中 manipulation一个 JSON 值时,它可以大于这个限制;该限制适用于服务器将其存储时。)您可以使用JSON_ STORAGE_SIZE()
函数来获取存储一个 JSON 文档所需的空间量;注意,对于一个JSON
列,存储大小—和因此该函数返回的值—是对该列进行任何部分更新之前使用的大小(请参阅本节后面的 JSON 部分更新优化讨论)。
除了JSON
数据类型外,还有一组 SQL 函数可用于在 JSON 值上执行操作,如创建、 manipulation 和搜索。以下讨论显示了这些操作的示例。关于个别函数的详细信息,请参阅第14.17节,“JSON 函数”。
还提供了一个用于操作GeoJSON值的空间函数集。请参阅第14.16.11节,“Spatial GeoJSON Functions”。
JSON
列,如其他二进制类型的列,不可以直接索引;相反,可以创建一个从JSON
列中提取标量值的生成列,并对其进行索引。请参阅Indexing a Generated Column to Provide a JSON Column Index,以获取详细示例。
MySQL 优化器还会寻找与JSON表达式匹配的虚拟列索引。
InnoDB
存储引擎支持在JSON数组上创建多值索引。请参阅Multi-Valued Indexes。
MySQL NDB集群支持JSON
列和MySQL JSON函数,包括在从JSON
列中生成的列上创建索引,以解决无法对JSON
列进行索引的问题。每个NDB
表最多支持3个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 列值存储在表中的部分更新和写入二进制日志的部分更新非常重要。可能会出现完整更新 JSON 列被记录到二进制日志中作为部分更新的情况。这可以发生在前两个条件之一不满足但其他条件满足的情况下。
请参阅binlog_row_value_options
的描述。
以下几节提供了基本信息关于 JSON 值的创建和操作。
JSON 数组包含由逗号分隔的值,enclosed 在[
和]
字符之间:
["abc", 10, null, true, false]
JSON 对象包含由逗号分隔的键-值对,enclosed 在{
和}
字符之间:
{"k1": "value", "k2": 10}
如示例所示,JSON 数组和对象可以包含字符串或数字的标量值、JSON null 文本、JSON 布尔值 true 或 false literals。JSON 对象中的键必须是字符串。时间(日期、时间或 datetime)标量值也被允许:
["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节,“Functions That Create JSON Values”),或者将其他类型的值转换为JSON
类型使用CAST(
(见将 JSON 和非 JSON 值之间进行转换)。下面几段描述了 MySQL 处理输入 JSON 值的方式。value
AS JSON)
在 MySQL 中,JSON 值被写作字符串。MySQL 将任何用于需要 JSON 值的上下文中的字符串解析,并在它不是有效的 JSON 时产生错误。这些上下文包括将值插入具有JSON
数据类型的列,以及将 JSON 值传递给期望 JSON 值的函数(通常在 MySQL JSON 函数的文档中显示为json_doc
或json_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,'.
在错误信息中出现的“at position
N
”位置是从0开始的,但应该被认为是问题实际所在位置的粗略指示。 -
函数
JSON_TYPE()
期望一个JSON参数,并尝试将其解析为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 在JSON上下文中使用utf8mb4
字符集和utf8mb4_bin
排序规则。其他字符集的字符串将被转换为utf8mb4
以适应JSON格式。(对于在ascii
或utf8mb3
字符集中使用的字符串,不需要转换,因为ascii
和utf8mb3
是utf8mb4
的子集。)
作为将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值的 normalize、merge 和 autowrapping。
(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的null
、true
和false
字面量,它们总是必须以小写形式书写:
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 NULL
、TRUE
和 FALSE
文本的大小写敏感性,这些文本可以以任何字母大小写书写:
mysql> SELECT ISNULL(null), ISNULL(Null), ISNULL(NULL);
+--------------+--------------+--------------+
| ISNULL(null) | ISNULL(Null) | ISNULL(NULL) |
+--------------+--------------+--------------+
| 1 | 1 | 1 |
+--------------+--------------+--------------+
有时可能需要或想在 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_UNQUOTE()
函数的描述,以了解这个模式对 JSON 值中的转义字符的影响。
当字符串被解析并发现是一个有效的 JSON 文档时,它也将被 normalize。这意味着,读取从左到右的文档中,遇到的成员键如果与后续文档中的键重复,将被丢弃。以下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"} |
+------------------------------------------------------+
normalization 也将在插入 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]} |
+------------------+
这项“last duplicate key wins”行为是由RFC 7159建议的,并且大多数JavaScript解析器都实现了。 (Bug #86866, Bug #26369555)
MySQL 会将原始JSON文档中的额外空格去除,或者在必要时插入一个单个空格,以便于显示。这是为了提高可读性。
MySQL 生成JSON值的函数(见第14.17.2节,“Functions That Create JSON Values”)总是返回标准化的值。
为了提高查找效率,MySQL 也会对JSON对象的键进行排序。您应该注意的是,这个顺序结果可能会改变,并且不保证在不同版本中的一致性。
支持两个合并算法,分别由函数JSON_MERGE_PRESERVE()
和JSON_MERGE_PATCH()
实现。这些函数在处理重复键时有所不同:JSON_MERGE_PRESERVE()
保留重复键的值,而JSON_MERGE_PATCH()
则丢弃所有except the last value。下面几段将解释这两个函数如何处理不同组合的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}
在需要数组值的上下文中使用非数组值将被自动包围:将该值 surround by [
和]
字符以将其转换为数组。在以下语句中,每个参数都将被自动包围为数组([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 文档中提取具有name
键的成员的值:
mysql> SELECT JSON_EXTRACT('{"id": 14, "name": "Aztalan"}', '$.name');
+---------------------------------------------------------+
| JSON_EXTRACT('{"id": 14, "name": "Aztalan"}', '$.name') |
+---------------------------------------------------------+
| "Aztalan" |
+---------------------------------------------------------+
路径语法使用leading $
字符来表示正在考虑的 JSON 文档,optional 后跟选择器,以指示文档中的更具体部分:
-
以点号开头的键名命名对象中的成员,该键名必须在双引号中指定,如果名称不包含引号且不是合法的路径表达式(例如,如果它包含空格)。
-
[
附加到选择数组的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
到N
]M
开始,到位置N
结束。last
也可以用作右侧数组元素的索引-relative addressing of array elements 也是支持的。如果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]
evaluates toNULL
(它引用的是第四个数组元素,但不存在)。
因为$[1]
和$[2]
的值不是标量值,所以它们可以用作选择嵌套值的基础路径表达式的依据。示例:
-
$[1].a
evaluates to[5, 6]
。 -
$[1].a[1]
evaluates to6
。 -
$[1].b
evaluates to10
。 -
$[2][0]
evaluates to99
。
如前所述,路径组件如果命名的键不是合法的路径表达式,可以使用引号来引用。让$
指向这个值:
{"a fish": "shark", "a bird": "sparrow"}
这两个键都包含空格,需要使用引号:
-
$."a fish"
evaluates toshark
。 -
$."a bird"
evaluates tosparrow
。
使用通配符的路径表达式将评估为包含多个值的数组:
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
到 N
M
和 N
分别是 JSON 数组中范围元素的第一个和最后一个索引。N
必须大于 M
;M
必须大于或等于 0。数组元素从 0 开始索引。
您可以在支持通配符的上下文中使用范围。
最右侧数组元素。 关键字last
支持作为最后一个数组元素的索引synonym。形式为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)
您可以使用
,将JSON列标识符和JSON路径表达式作为column
->path
JSON_EXTRACT()
的同义词。请参阅第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]
的实例匹配false
在b
元素中,并删除它。 -
第二个
$[1].b[1]
的实例匹配 nothing:该元素已经被删除,路径不再存在,对其没有影响。
MySQL 中许多 JSON 函数(见第14.17节,“JSON Functions”)需要路径表达式以确定 JSON 文档中的特定元素。路径由作用域和一个或多个路径腿组成。用于 MySQL JSON 函数的路径中,作用域总是正在搜索或操作的文档,表示为带有leading $
字符的路径。路径腿之间使用点字符(.
)。数组中的单元格由[
表示,其中N
]N
是非负整数。键名必须是双引号字符串或有效的 ECMAScript 标识符(见Identifier Names and Identifiers,在 ECMAScript Language Specification 中)。路径表达式,如 JSON 文本,应该使用ascii
、utf8mb3
或utf8mb4
字符集编码。其他字符编码将被隐式转换为 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 不支持这些。
通配符 *
和 **
token 用于以下方式:
-
.*
表示对象中的所有成员的值。 -
[*]
表示数组中的所有单元的值。 -
[
表示以prefix
]**suffix
prefix
开头,结尾为suffix
的所有路径。prefix
可选,而suffix
是必需的;换言之,路径不能以**
结尾。此外,路径也不能包含序列
***
。
关于路径语法示例,请参阅各种 JSON 函数的描述,这些函数将路径作为参数,如 JSON_CONTAINS_PATH()
、JSON_SET()
和 JSON_REPLACE()
。关于使用 *
和 **
通配符的示例,请参阅 JSON_SEARCH()
函数的描述。
MySQL 还支持使用 to
关键字(如 $[2 to 10]
)来表示 JSON 数组的子集,以及使用 last
关键字作为数组右侧元素的同义词。请参阅 Searching and Modifying JSON Values,了解更多信息和示例。
可以使用=
、<
、<=
、>
、>=
、<>
、!=
和<=>
操作符来比较JSON值。
以下比较操作符和函数当前不支持与JSON值的比较:
对上述比较操作符和函数的解决方法是将JSON值转换为native MySQL数字或字符串数据类型,以便它们具有consistent non-JSON scalar type。
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的
DATETIME
和TIMESTAMP
类型,分别表示同一时间点,则它们相等。 -
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
个字节进行字典顺序比较,whereN
是较短字符串的长度。如果前N
个字节相同,则较短字符串被认为小于较长字符串。示例:
"a" < "ab" < "b" < "bc"
这种顺序与使用collation
utf8mb4_bin
的SQL字符串顺序相等。由于utf8mb4_bin
是二进制排序,因此JSON值的比较是区分大小写的:"A" < "a"
-
INTEGER
,DOUBLE
JSON值可以包含精确值数字和近似值数字。关于这些类型数字的总体讨论,请见第11.1.2节,“数字字面量”。
native MySQL数值类型的比较规则在第14.3节,“表达式求值中的类型转换”中讨论,但JSON值中的数字比较规则有一些不同:
-
在使用native MySQL
INT
和DOUBLE
数值类型的两个列之间进行比较时,知道所有比较都涉及到整数和双精度浮点数,因此将整数转换为双精度浮点数,以便于所有行。也就是说,精确值数字被转换为近似值数字。 -
另一方面,如果查询比较两个JSON列,其中包含数字,那么无法提前知道这些数字是否是整数或双精度浮点数。为了在所有行中提供最一致的行为,MySQL将近似值数字转换为精确值数字。结果的排序是一致的,不会失去精确值数字的精度。例如,给定标量9223372036854775805、9223372036854775806、9223372036854775807和9.223372036854776e18,顺序如下:
9223372036854775805 < 9223372036854775806 < 9223372036854775807 < 9.223372036854776e18 = 9223372036854776000 < 9223372036854776001
如果使用非JSON数值比较规则,则可能会出现不一致的排序。MySQL通常的数字比较规则为这些顺序:
-
整数比较:
9223372036854775805 < 9223372036854775806 < 9223372036854775807
(对9.223372036854776e18无定义)
-
双精度浮点数比较:
9223372036854775805 = 9223372036854775806 = 9223372036854775807 = 9.223372036854776e18
-
对于任何JSON值与SQLNULL
的比较结果为UNKNOWN
。
对于JSON和非JSON值之间的比较,非JSON值将根据以下表格规则转换为JSON,然后按照之前描述的方式进行比较。
以下表格总结了 MySQL 在将 JSON 值转换为其他类型值时遵循的规则:
表13.3 JSON 转换规则
other type | CAST(other type AS JSON) | CAST(JSON AS other type) |
---|---|---|
JSON | ||
utf8 character type (utf8mb4 , utf8mb3 , ascii ) |
将字符串解析为 JSON 值。 | 将 JSON 值序列化为 utf8mb4 字符串。 |
Other character types | 其他字符编码隐式转换为 utf8mb4 并按该字符类型的描述处理。 |
将 JSON 值序列化为 utf8mb4 字符串,然后将其转换为其他字符编码。结果可能无意义。 |
NULL |
结果是一个 NULL 值类型的 JSON 值。 |
|
Geometry types | 将几何值转换为 JSON 文档,通过调用ST_AsGeoJSON() 。 |
非法操作。解决方案:将CAST( 的结果传递给ST_GeomFromGeoJSON() 。 |
All other types | 结果是一个包含单个标量值的 JSON 文档。 | 如果 JSON 文档包含目标类型的单个标量值,并且该标量值可以被转换为目标类型,则成功。否则,返回 NULL 并产生警告。 |
ORDER BY
和 GROUP BY
对 JSON 值的工作原理如下:
-
标量 JSON 值的排序规则与前面讨论的一致。
-
对于升序排序,SQL
NULL
将在所有 JSON 值之前排序,包括 JSON null 字面量;对于降序排序,SQLNULL
将在所有 JSON 值之后排序,包括 JSON null 字面量。 -
JSON 值的排序键由系统变量
max_sort_length
确定,因此只在前max_sort_length
字节不同的键将被视为相等。 -
当前不支持非标量值的排序,并且会出现警告。
对排序有益的是,可以将 JSON 标量强制转换为 MySQL 的其他native类型。例如,如果一个名为 jdoc
的列包含 JSON 对象,其中的一个成员是具有非负值的 id
键,可以使用以下表达式对 id
值进行排序:
ORDER BY CAST(JSON_EXTRACT(jdoc, '$.id') AS UNSIGNED)
如果在 ORDER BY
中出现了生成列定义,以相同的表达式作为查询执行计划,则 MySQL 优化器会识别出这个情况,并考虑使用索引。见第10.3.11节,“Optimizer Use of Generated Column Indexes”。
对于JSON值的聚合,SQLNULL
值将被忽略,就像对其他数据类型一样。非NULL
值将被转换为数字类型并聚合,除非是MIN()
、MAX()
和GROUP_CONCAT()
。将JSON值转换为数字类型应该能够产生有意义的结果,对于numeric scalar JSON值来说,可能会出现截断和精度损失的问题。其他JSON值的转换为数字类型可能不会产生有意义的结果。