JSON_TABLE是MySQL 8.0+中唯一能将JSON数组展开为多行关系结果集的函数,必须用于需对JSON数组元素逐项JOIN、WHERE筛选或聚合的场景。

JSON_TABLE 是什么,什么时候必须用它
JSON_TABLE 不是“可选替代方案”,而是 MySQL 中唯一能把 JSON 数组展开成标准行集(即多行结果)的函数。如果你的 JSON 字段存的是数组(比如 [{"id":1,"name":"a"},{"id":2,"name":"b"}]),而你想对每个元素做 JOIN、加 WHERE 条件筛选、或与其他表关联,JSON_EXTRACT 或 -> 操作符只能返回单个值或 NULL,没法生成多行 —— 这时候就必须上 JSON_TABLE。
常见错误现象:
- 用
JSON_EXTRACT(json_col, '$[0]')只能取第一个对象,想遍历全部?手动写 N 个$[0],$[1]… 不现实 - 用
SELECT ... FROM t, JSON_TABLE(...)却没加LATERAL或写错别名,报错Unknown column 't.json_col' in 'field list'
使用场景明确包括:
- 日志字段里存了多个标签
["bug", "ui", "backend"],要统计各标签出现次数 - 用户权限字段是对象数组
[{"role":"admin"},{"role":"editor"}],需查出所有含"admin"的用户 - 订单明细以 JSON 数组存在主表字段中,不想拆表也要支持按商品 ID 聚合
怎么写一个可用的 JSON_TABLE 查询
核心结构固定:JSON_TABLE(json_expr, path COLUMNS (col_def, ...)) AS alias,漏任何一部分都语法报错。
关键参数差异和实操建议:
-
json_expr必须是合法 JSON 字符串或列名,如果是字段,建议先用ISJSON()或JSON_VALID()过滤,避免解析失败导致整行丢弃 -
path写$[*]表示遍历整个数组;若只想处理前 3 个元素,得先用JSON_EXTRACT(json_col, '$[0 to 2]')截取再传入 -
COLUMNS里每个col_def格式为col_name type PATH '$.key' [ORDINALITY | EXISTS],注意:-
type必须显式声明(如VARCHAR(50),INT),不能写TEXT(MySQL 不支持) -
PATH是相对于当前数组元素的路径,不是整个 JSON 的根路径 - 加
ORDINALITY会多出一列序号(从 1 开始),方便排序或去重
-
示例(提取用户权限数组中的所有 role):
SELECT u.id, jt.role FROM users u, JSON_TABLE(u.permissions, '$[*]' COLUMNS (role VARCHAR(20) PATH '$.role') ) AS jt WHERE jt.role = 'admin';
容易踩的坑:NULL、空数组、嵌套结构怎么处理
JSON_TABLE 对非法输入非常严格,稍不注意就静默丢数据:
- 如果
json_expr是NULL或非数组(比如传了个对象{}),整行不会出现在结果中,也不会报错 —— 建议在外层用WHERE JSON_TYPE(permissions) = 'ARRAY'预过滤 - 空数组
[]会导致JSON_TABLE不生成任何行,这符合预期,但容易被误判为“没数据”而非“数组为空” - 嵌套太深(如
$.items[<em>].details[</em>].price)不能直接两层$[*]套用,必须分步:先用外层JSON_TABLE展开items,再对每行的details字段单独调一次JSON_TABLE
性能提示:
-
JSON_TABLE无法走索引,纯内存解析,字段越大越慢;如果高频查询某 key,优先建虚拟列 + 索引(如ALTER TABLE users ADD COLUMN first_role VARCHAR(20) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(permissions, '$[0].role'))) STORED) - 不要用
JSON_TABLE处理超长数组(>100 元素),考虑应用层拆分或预计算
复杂点在于:它不提供“跳过解析失败项”的开关,一旦某个数组元素的 PATH 找不到字段(比如 $.role 不存在),该行直接被忽略,连 NULL 都不给 —— 这和 JSON_EXTRACT 的宽容行为完全不同。


















