postgresql中需用lateral jsonb_array_elements()展开json数组后逐元素过滤,mysql则用json_table()映射为虚拟表;二者均不可直接在where中对json数组字段用like或=匹配,且路径错误会静默返回空结果。

PostgreSQL 中用 jsonb_array_elements() 展开数组再筛选
直接在 WHERE 里对 JSON 数组字段写 LIKE 或 = 是无效的——JSON 数组不是字符串,不能整体匹配。必须先“打散”成行,再逐元素过滤。
比如表 orders 有个 items 字段是 jsonb 类型,存的是商品数组:[{"id": 101, "status": "shipped"}, {"id": 102, "status": "pending"}],要查含 "status": "pending" 的订单:
SELECT DISTINCT o.* FROM orders o, LATERAL jsonb_array_elements(o.items) AS item WHERE item->>'status' = 'pending';
-
LATERAL是关键:让jsonb_array_elements()能引用外层的o.items -
item->>'status'用->>提取字符串值;若要取布尔或数字,用->再转类型(如(item->'in_stock')::boolean) - 不用
DISTINCT会重复:一个订单含多个 pending 项,就返回多行
MySQL 8.0+ 用 JSON_TABLE() 把数组转虚拟表
MySQL 没有类似 jsonb_array_elements() 的函数,但 JSON_TABLE() 可以把 JSON 数组映射成临时关系表,这是唯一可靠方式。
同样场景:items 是 JSON 字符串字段(注意 MySQL 默认是 JSON 类型,不是 jsonb),想筛选含 pending 商品的订单:
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
SELECT o.* FROM orders o
JOIN JSON_TABLE(
o.items,
'$[*]' COLUMNS (
id INT PATH '$.id',
status TEXT PATH '$.status'
)
) AS jt ON jt.status = 'pending';
-
'$[*]'表示遍历数组每个元素;路径写错(比如写成'$')会导致整行为空 -
COLUMNS里定义的别名(如status)才能在ON或WHERE中引用 - 如果数组为空或
NULL,JSON_TABLE()默认不生成行——这点和 PostgreSQL 的jsonb_array_elements()行为一致
避免在子查询里重复解析 JSON 字段
常见错误是写两层子查询,外层查 ID,内层又对同一 JSON 字段调用 jsonb_array_elements() 或 JSON_TABLE(),导致性能断崖式下降。
- PostgreSQL:用
LATERAL一次展开,不要写EXISTS (SELECT FROM jsonb_array_elements(...)) - MySQL:别在
WHERE里嵌套JSON_CONTAINS()去查数组——它只能做存在性判断,无法提取字段值做等值/范围筛选 - 真正需要多条件时(比如 status=pending 且 id > 100),把所有条件都压到
JOIN后的WHERE,而不是拆成多个子查询
JSON 路径错误导致空结果却无报错
路径写错不会报错,只会返回空集,非常难排查。比如数组实际是 [{"product": {"name": "A"}}],却写了 $.name —— 这种错法静默失败。
- PostgreSQL:用
jsonb_path_exists(o.items, '$[*].status == "pending"')先验证路径是否命中,再展开 - MySQL:用
JSON_EXTRACT(items, '$[0].status')手动试一条数据,确认路径语法和层级 - 特别注意引号:MySQL 路径中字符串要用双引号,PostgreSQL 的
jsonpath也要求双引号,单引号会直接语法错误
数组嵌套越深,路径越容易出错;没验证过路径就写完整查询,大概率白忙半小时。










