首页/数据/SQL 速查/展开 JSON 数组为多行(JSON_TABLE 与 LATERAL)

展开 JSON 数组为多行(JSON_TABLE 与 LATERAL)

把一行里的 JSON 数组拆成多行参与过滤与统计:MySQL 8 用 JSON_TABLE,PostgreSQL 用 jsonb_array_elements 配合 LATERAL。

SQL 速查数据更新于 2026-10-13

把 JSON 数组拆成多行

同一条记录里的 JSON 数组要参与过滤和统计,就得先展开成多行:MySQL 8 用 JSON_TABLE 把 JSON 文档当表来查,PostgreSQL 用 jsonb_array_elements_text 这类集合返回函数配合 LATERAL 横向展开。展开后行数由数组长度决定,是处理去规范化 JSON 数据最常用的手法。

基本信息

MySQL 写法FROM 表, JSON_TABLE(列, '$[*]' COLUMNS (… PATH '…')) AS 别名
PostgreSQL 写法CROSS JOIN LATERAL jsonb_array_elements_text(列) AS t(值)
结果行数等于原行数乘以数组元素个数
索引展开出来的值通常用不上普通索引
空数组该行不产生任何结果行

典型使用场景

  • 按 JSON 数组里的标签筛选订单或用户
  • 把订单里的商品明细展开成行做统计
  • 统计每个标签被多少条记录引用
  • 把 JSON 日志展开成可查询的行集
  • 替代应用层循环解析 JSON 再拼 SQL 的做法

MySQL 8 写法

MySQL 8 写法
-- 把 tags 数组展开成多行,再按标签过滤
SELECT o.id, jt.tag
FROM orders o,
     JSON_TABLE(o.tags, '$[*]'
                COLUMNS (tag VARCHAR(64) PATH '$')) AS jt
WHERE jt.tag = 'vip';

-- 统计每个标签覆盖的订单数(注意用 DISTINCT 还原订单口径)
SELECT jt.tag, COUNT(DISTINCT o.id) AS orders
FROM orders o,
     JSON_TABLE(o.tags, '$[*]' COLUMNS (tag VARCHAR(64) PATH '$')) AS jt
GROUP BY jt.tag;

PostgreSQL 写法

PostgreSQL 写法
-- 展开 jsonb 数组为行,再用 LATERAL 横向连接
SELECT o.id, t.tag
FROM orders o
CROSS JOIN LATERAL jsonb_array_elements_text(o.tags) AS t(tag)
WHERE t.tag = 'vip';

-- 统计每个标签覆盖的订单数
SELECT t.tag, COUNT(DISTINCT o.id) AS orders
FROM orders o
CROSS JOIN LATERAL jsonb_array_elements_text(o.tags) AS t(tag)
GROUP BY t.tag;

两库写法对照

维度MySQL 8PostgreSQL
展开方式JSON_TABLEjsonb_array_elements_text
连接写法逗号连接(隐式 CROSS JOIN)CROSS JOIN LATERAL
取对象字段COLUMNS 里用 PATH 指定用 ->> 取字段值
空数组该行不产生结果行同样不产生结果行
保留原行需改写为 LEFT JOIN 形式改用 LEFT JOIN LATERAL
展开会改变聚合口径:展开前一行代表一张订单,展开后一行代表一个标签,COUNT(*) 的含义完全不同。统计订单数必须写成 COUNT(DISTINCT 订单号)。
JSON 列里的字段通常用不上普通索引:高频按标签查的场景,考虑拆成关联表,或在生成列上建索引来加速。
展开函数对空数组与 NULL 的处理要提前想清楚:两库遇到空数组都不产生任何行,业务若需要保留这些记录,就要先 COALESCE 成空数组再展开。
展开后的行数由数组长度决定:个别超长数组会把结果集明显撑大,做聚合前先看一眼长度分布,避免少数记录拖慢整条查询。

易错点

  • JSON 列内字段检索用不上普通索引:高频查询要靠拆表或生成列加索引,否则每次都要全表展开
  • 展开后聚合口径被改变:订单数被算成标签数,需要 COUNT(DISTINCT 订单号) 才能还原
  • 空数组的行被过滤掉:CROSS JOIN 或逗号连接会让空数组的行直接消失,需要保留时改用 LEFT JOIN LATERAL
  • 忽略 JSON 合法性:非数组或非法 JSON 会让展开函数报错或返回空,写入时就该校验格式
  • 在大表上无过滤地展开:行数被放大数十倍,再叠加排序或分组会非常慢,先用 WHERE 缩小输入

数据来源与参考

  • MySQL 8.0 Reference Manual · JSON_TABLE
  • PostgreSQL Documentation · JSON Functions
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌