很多人第一次查 SQL 里的 JSON 数组,都会遇到同一个现象:语句看起来没错,结果却始终查不到。问题通常不在函数本身,而在字段是不是原生 JSON 类型、路径是否明确指向数组元素,以及查询值的类型有没有写对。
这篇文章按“为什么查不到、怎么取指定元素、批量筛选时该怎么选、不同数据库写法有什么差异”四个问题展开。读完后,你可以判断什么时候继续用 JSON_CONTAINS,什么时候该换成 JSON_TABLE 或 PostgreSQL 的 jsonb 操作符。
MySQL 里 JSON_CONTAINS 为什么查不到数组值
JSON_CONTAINS 在 MySQL 里查 JSON 数组时,经常被误以为“函数不稳定”。实际更常见的原因只有两个:字段并不是 JSON 类型,而是用 VARCHAR 或 TEXT 存了一段字符串;或者路径参数漏掉了 $[*],导致函数只在顶层结构匹配,不会自动遍历数组元素。

先检查字段类型和路径写法
- 字段必须是
JSON类型,而不是VARCHAR或TEXT;否则JSON_CONTAINS可能返回NULL,条件无法成立。 - 查询数组中是否包含某个值时,要显式指定数组路径:
JSON_CONTAINS(json_col, '"value"', '$[*]') $[*]的作用是匹配数组里的每一个元素;如果省略,函数不会帮你遍历数组。
字符串和数字的写法不能混用
JSON 查询对类型匹配比较严格。字符串值和数字值的写法不同,不能指望数据库自动帮你做隐式转换。
- 字符串要保留 JSON 字符串格式:
JSON_CONTAINS(json_col, '"value"', '$[*]') - 数字不要再包一层引号,例如:
JSON_CONTAINS(json_col, 42, '$[*]')
如果数组里实际存的是 [1,"2",true] 这种混合类型,查询结果更容易出现“看起来相同,实际匹配不上”的情况。
想取数组第 N 个元素,MySQL 应该怎么写
如果你的需求不是“数组里有没有某个值”,而是“拿到第几个元素”,更适合用 JSON_EXTRACT。它支持标准数组下标访问,但有两个行为要先记住:MySQL 数组下标从 0 开始;访问越界时返回 NULL,不会报错。
按下标读取数组元素
- 取第一个元素:
JSON_EXTRACT(data, '$[0]') - 取最后一个元素,需要配合
JSON_LENGTH计算下标:JSON_EXTRACT(data, CONCAT('$[', JSON_LENGTH(data) - 1, ']'))
结果格式和空值处理要提前考虑
JSON_EXTRACT 返回的是 JSON 值本身,因此字符串结果会带双引号,数字和布尔值则不会。如果你需要普通文本结果,可以再套一层 JSON_UNQUOTE()。
- 去掉字符串结果的双引号:
JSON_UNQUOTE(JSON_EXTRACT(data, '$[0]')) - 字段为空、不是数组或下标越界时,结果通常是
NULL - 在业务查询里,建议用
COALESCE做兜底,避免后续逻辑直接吃到空值
筛选包含某个值的记录时,别只盯着 JSON_CONTAINS
当需求变成“找出所有数组里包含某个标签的记录”时,JSON_CONTAINS 虽然能写,但它更适合小规模、临时性过滤。原因是这种写法通常性能一般,也难以利用索引。

JSON_CONTAINS 适合简单判断,JSON_TABLE 更适合可扩展查询
最直接的写法是这样:
WHERE JSON_CONTAINS(tags, '"urgent"', '$[*]')它的优点是语句短、上手快,缺点是更容易走全表扫描。对于数据量较大的表,MySQL 8.0+ 更稳妥的方案通常是把数组展开成行,再按普通列过滤。
SELECT t.* FROM orders t,
JSON_TABLE(t.tags, '$[*]' COLUMNS(tag VARCHAR(20) PATH '$')) AS jt
WHERE jt.tag = 'urgent'这种方式的核心思路,是先把 tags 数组拆成多行 tag,再进行标准筛选。如果再结合生成列与索引,查询可维护性和性能都会更好一些。
使用 JSON_TABLE 前要先做合法性校验
JSON_TABLE 有一个很实际的前提:输入必须是合法 JSON 数组。空值、损坏 JSON 或非数组内容,都可能直接报错。
- 使用前建议确认字段内容是合法 JSON
- 必要时可增加
JSON_VALID(t.tags)作为安全校验
PostgreSQL 查询 JSONB 数组更直接
如果你用的是 PostgreSQL,处理 JSON 数组通常会比 MySQL 顺手一些。原因是 jsonb 提供了更直接的操作符,不必频繁手写路径表达式。

常用 jsonb 操作符和写法
- 查数组是否包含某个字符串:
tags @> '["urgent"]'::jsonb - 查对象数组中是否包含某种结构:
items @> '[{"type":"error"}]'::jsonb - 把数组元素展开成行:
SELECT jsonb_array_elements(tags) FROM logs - 按对象字段过滤数组元素:
WHERE tags @? '$[*] ? (@.status == "failed")'
在表达“数组里是否包含某个对象”或“对象数组里某字段是否满足条件”时,PostgreSQL 的 jsonb 语法通常更紧凑,也更容易读懂。
最后要注意的 3 类常见坑
无论是 MySQL 还是 PostgreSQL,真正容易出问题的都不是函数名,而是数据本身。
最常见的失配来源
- 数组深度不一致:你以为查的是一维数组,实际字段里可能嵌套了更深一层结构。
- 空值和非法内容:空字段、非数组 JSON、损坏 JSON,都会让提取或展开结果变成
NULL,甚至直接报错。 - 类型混杂:例如
[1,"2",true]这样的数组,字符串"2"和数字2并不等价;MySQL 不会自动帮你转换,PostgreSQL 也要求严格匹配。
实际写查询时,可以先确认三件事:字段是不是原生 JSON 类型、路径是否真的指向数组元素、查询值的类型是否和数组内容完全一致。大多数“明明有值却查不到”的问题,最后都能落到这三个检查点上。









