本节包含有关将 JSON 数据转换为表格数据的 JSON 函数的信息。MySQL 9.0 支持一个这样的函数,JSON_TABLE()
。
JSON_TABLE(
expr
, path
COLUMNS (column_list
) [AS] alias
)
从 JSON 文档中提取数据,并将其作为具有指定列的关系表返回。此函数的完整语法如下所示
JSON_TABLE(
expr,
path COLUMNS (column_list)
) [AS] alias
column_list:
column[, column][, ...]
column:
name FOR ORDINALITY
| name type PATH string path [on_empty] [on_error]
| name type EXISTS PATH string path
| NESTED [PATH] path COLUMNS (column_list)
on_empty:
{NULL | DEFAULT json_string | ERROR} ON EMPTY
on_error:
{NULL | DEFAULT json_string | ERROR} ON ERROR
expr
:这是一个返回 JSON 数据的表达式。它可以是常量 ('{"a":1}'
)、列 (t1.json_data
,假设在 FROM
子句中指定了表 t1
优先于 JSON_TABLE()
),或函数调用 (JSON_EXTRACT(t1.json_data,'$.post.comments')
)。
path
:一个 JSON 路径表达式,应用于数据源。我们将与路径匹配的 JSON 值称为 行源;它用于生成关系数据行。COLUMNS
子句评估行源,在行源中查找特定的 JSON 值,并将这些 JSON 值作为 SQL 值返回到关系数据行的各个列中。
alias
是必需的。表别名的通常规则适用(请参阅 第 11.2 节,“模式对象名称”)。
此函数以不区分大小写的方式比较列名。
JSON_TABLE()
支持四种类别的列,如下列表所示
:此类型枚举name
FOR ORDINALITYCOLUMNS
子句中的行;名为name
的列是一个计数器,其类型为UNSIGNED INT
,初始值为 1。这相当于在CREATE TABLE
语句中将列指定为AUTO_INCREMENT
,并且可以用于区分具有相同值的父行,这些行是由NESTED [PATH]
子句生成的多个行。
:此类型的列用于提取由name
type
PATHstring_path
[on_empty
] [on_error
]string_path
指定的值。type
是 MySQL 标量数据类型(也就是说,它不能是对象或数组)。JSON_TABLE()
将数据提取为 JSON,然后将其强制转换为列类型,使用适用于 MySQL 中 JSON 数据的常规自动类型转换。缺少值会触发on_empty
子句。保存对象或数组会触发可选的on error
子句;当在将保存为 JSON 的值强制转换为表列期间发生错误时也会发生这种情况,例如尝试将字符串'asd'
保存到整数列。
:如果在name
type
EXISTS PATHpath
path
指定的位置存在任何数据,则此列返回 1,否则返回 0。type
可以是任何有效的 MySQL 数据类型,但通常应指定为INT
的某种变体。NESTED [PATH]
:这会将 JSON 数据中的嵌套对象或数组与父对象或数组中的 JSON 值一起展平为单行。使用多个path
COLUMNS (column_list
)PATH
选项可以将来自多级嵌套的 JSON 值投影到单行中。path
相对于JSON_TABLE()
的父路径行路径,或者在嵌套路径的情况下相对于父NESTED [PATH]
子句的路径。
如果指定了 on empty
,则它确定在缺少数据的情况下 JSON_TABLE()
的行为(取决于类型)。当 NESTED PATH
子句中的列没有匹配项并为其生成 NULL
补充行时,也会触发此子句。on empty
采用以下值之一
NULL ON EMPTY
:该列设置为NULL
;这是默认行为。DEFAULT
:提供的json_string
ON EMPTYjson_string
将被解析为 JSON(只要它是有效的),并存储起来以代替缺少的值。列类型规则也适用于默认值。ERROR ON EMPTY
:抛出错误。
如果使用,on_error
采用以下值之一,并显示相应的结果:
NULL ON ERROR
:该列设置为NULL
;这是默认行为。DEFAULT
:json string
ON ERRORjson_string
将被解析为 JSON(只要它是有效的),并存储起来以代替对象或数组。ERROR ON ERROR
:抛出错误。
在 ON EMPTY
之前指定 ON ERROR
是非标准的,并且在 MySQL 中已弃用;尝试这样做会导致服务器发出警告。预计在未来版本的 MySQL 中将删除对非标准语法的支持。
当保存到列的值被截断时,例如在 DECIMAL(10,1)
列中保存 3.14159,会发出警告,而与任何 ON ERROR
选项无关。当单个语句中有多个值被截断时,警告只发出一次。
当传递给此函数的表达式和路径解析为 JSON null 时,JSON_TABLE()
将返回 SQL NULL
,这符合 SQL 标准,如下所示
mysql> SELECT *
-> FROM
-> JSON_TABLE(
-> '[ {"c1": null} ]',
-> '$[*]' COLUMNS( c1 INT PATH '$.c1' ERROR ON ERROR )
-> ) as jt;
+------+
| c1 |
+------+
| NULL |
+------+
1 row in set (0.00 sec)
以下查询演示了 ON EMPTY
和 ON ERROR
的用法。路径 "$.a"
对应的行 {"b":1}
为空,并且尝试将 [1,2]
保存为标量会产生错误;这些行在所示的输出中突出显示。
mysql> SELECT *
-> FROM
-> JSON_TABLE(
-> '[{"a":"3"},{"a":2},{"b":1},{"a":0},{"a":[1,2]}]',
-> "$[*]"
-> COLUMNS(
-> rowid FOR ORDINALITY,
-> ac VARCHAR(100) PATH "$.a" DEFAULT '111' ON EMPTY DEFAULT '999' ON ERROR,
-> aj JSON PATH "$.a" DEFAULT '{"x": 333}' ON EMPTY,
-> bx INT EXISTS PATH "$.b"
-> )
-> ) AS tt;
+-------+------+------------+------+
| rowid | ac | aj | bx |
+-------+------+------------+------+
| 1 | 3 | "3" | 0 |
| 2 | 2 | 2 | 0 |
| 3 | 111 | {"x": 333} | 1 |
| 4 | 0 | 0 | 0 |
| 5 | 999 | [1, 2] | 0 |
+-------+------+------------+------+
5 rows in set (0.00 sec)
列名遵循管理表列名的常用规则和限制。请参阅 第 11.2 节“架构对象名称”。
将检查所有 JSON 和 JSON 路径表达式的有效性;两种类型的无效表达式都会导致错误。
COLUMNS
关键字前面的 path
的每个匹配项都映射到结果表中的一行。例如,以下查询给出了此处显示的结果
mysql> SELECT *
-> FROM
-> JSON_TABLE(
-> '[{"x":2,"y":"8"},{"x":"3","y":"7"},{"x":"4","y":6}]',
-> "$[*]" COLUMNS(
-> xval VARCHAR(100) PATH "$.x",
-> yval VARCHAR(100) PATH "$.y"
-> )
-> ) AS jt1;
+------+------+
| xval | yval |
+------+------+
| 2 | 8 |
| 3 | 7 |
| 4 | 6 |
+------+------+
表达式 "$[*]"
匹配数组中的每个元素。您可以通过修改路径来过滤结果中的行。例如,使用 "$[1]"
将提取限制为用作源的 JSON 数组的第二个元素,如下所示
mysql> SELECT *
-> FROM
-> JSON_TABLE(
-> '[{"x":2,"y":"8"},{"x":"3","y":"7"},{"x":"4","y":6}]',
-> "$[1]" COLUMNS(
-> xval VARCHAR(100) PATH "$.x",
-> yval VARCHAR(100) PATH "$.y"
-> )
-> ) AS jt1;
+------+------+
| xval | yval |
+------+------+
| 3 | 7 |
+------+------+
在列定义中,"$"
将整个匹配项传递给该列;"$.x"
和 "$.y"
仅传递该匹配项中与键 x
和 y
对应的值。有关更多信息,请参阅 JSON 路径语法。
NESTED PATH
(或简称为 NESTED
;PATH
是可选的)为 COLUMNS
子句中它所属的每个匹配项生成一组记录。如果没有匹配项,则嵌套路径的所有列都设置为 NULL
。这将在最顶层子句和 NESTED [PATH]
之间实现外连接。可以通过在 WHERE
子句中应用适当的条件来模拟内连接,如下所示
mysql> SELECT *
-> FROM
-> JSON_TABLE(
-> '[ {"a": 1, "b": [11,111]}, {"a": 2, "b": [22,222]}, {"a":3}]',
-> '$[*]' COLUMNS(
-> a INT PATH '$.a',
-> NESTED PATH '$.b[*]' COLUMNS (b INT PATH '$')
-> )
-> ) AS jt
-> WHERE b IS NOT NULL;
+------+------+
| a | b |
+------+------+
| 1 | 11 |
| 1 | 111 |
| 2 | 22 |
| 2 | 222 |
+------+------+
同级嵌套路径(即,同一 COLUMNS
子句中的两个或多个 NESTED [PATH]
实例)将依次处理,一次一个。当一个嵌套路径生成记录时,任何同级嵌套路径表达式的列都设置为 NULL
。这意味着单个包含 COLUMNS
子句中单个匹配项的记录总数是 NESTED [PATH]
修饰符生成的所有记录的总和,而不是乘积,如下所示
mysql> SELECT *
-> FROM
-> JSON_TABLE(
-> '[{"a": 1, "b": [11,111]}, {"a": 2, "b": [22,222]}]',
-> '$[*]' COLUMNS(
-> a INT PATH '$.a',
-> NESTED PATH '$.b[*]' COLUMNS (b1 INT PATH '$'),
-> NESTED PATH '$.b[*]' COLUMNS (b2 INT PATH '$')
-> )
-> ) AS jt;
+------+------+------+
| a | b1 | b2 |
+------+------+------+
| 1 | 11 | NULL |
| 1 | 111 | NULL |
| 1 | NULL | 11 |
| 1 | NULL | 111 |
| 2 | 22 | NULL |
| 2 | 222 | NULL |
| 2 | NULL | 22 |
| 2 | NULL | 222 |
+------+------+------+
FOR ORDINALITY
列枚举 COLUMNS
子句生成的记录,并且可以用来区分嵌套路径的父记录,尤其是在父记录中的值相同的情况下,如下所示
mysql> SELECT *
-> FROM
-> JSON_TABLE(
-> '[{"a": "a_val",
'> "b": [{"c": "c_val", "l": [1,2]}]},
'> {"a": "a_val",
'> "b": [{"c": "c_val","l": [11]}, {"c": "c_val", "l": [22]}]}]',
-> '$[*]' COLUMNS(
-> top_ord FOR ORDINALITY,
-> apath VARCHAR(10) PATH '$.a',
-> NESTED PATH '$.b[*]' COLUMNS (
-> bpath VARCHAR(10) PATH '$.c',
-> ord FOR ORDINALITY,
-> NESTED PATH '$.l[*]' COLUMNS (lpath varchar(10) PATH '$')
-> )
-> )
-> ) as jt;
+---------+---------+---------+------+-------+
| top_ord | apath | bpath | ord | lpath |
+---------+---------+---------+------+-------+
| 1 | a_val | c_val | 1 | 1 |
| 1 | a_val | c_val | 1 | 2 |
| 2 | a_val | c_val | 1 | 11 |
| 2 | a_val | c_val | 2 | 22 |
+---------+---------+---------+------+-------+
源文档包含一个包含两个元素的数组;这些元素中的每一个都会生成两行。apath
和 bpath
的值在整个结果集中都相同;这意味着它们不能用于确定 lpath
值来自相同还是不同的父级。当 top_ord
等于 1 时,ord
列的值保持不变,因此这两个值来自同一个对象。其余两个值来自不同的对象,因为它们在 ord
列中的值不同。
通常,您不能连接派生表,该派生表依赖于同一 FROM
子句中前面表的列。根据 SQL 标准,MySQL 为表函数做了一个例外;这些被认为是横向派生表。这是隐式的,因此根据标准,在 JSON_TABLE()
之前不允许这样做。
假设您有一个使用此处显示的语句创建并填充的表 t1
CREATE TABLE t1 (c1 INT, c2 CHAR(1), c3 JSON);
INSERT INTO t1 () VALUES
ROW(1, 'z', JSON_OBJECT('a', 23, 'b', 27, 'c', 1)),
ROW(1, 'y', JSON_OBJECT('a', 44, 'b', 22, 'c', 11)),
ROW(2, 'x', JSON_OBJECT('b', 1, 'c', 15)),
ROW(3, 'w', JSON_OBJECT('a', 5, 'b', 6, 'c', 7)),
ROW(5, 'v', JSON_OBJECT('a', 123, 'c', 1111))
;
然后,您可以执行连接,例如这个,其中 JSON_TABLE()
充当派生表,同时引用先前引用的表中的列
SELECT c1, c2, JSON_EXTRACT(c3, '$.*')
FROM t1 AS m
JOIN
JSON_TABLE(
m.c3,
'$.*'
COLUMNS(
at VARCHAR(10) PATH '$.a' DEFAULT '1' ON EMPTY,
bt VARCHAR(10) PATH '$.b' DEFAULT '2' ON EMPTY,
ct VARCHAR(10) PATH '$.c' DEFAULT '3' ON EMPTY
)
) AS tt
ON m.c1 > tt.at;
尝试在此查询中使用 LATERAL
关键字会引发 ER_PARSE_ERROR
。