
本文详解在 symfony + postgresql 环境下,如何安全、高效地查询 `json`/`jsonb` 类型的 `roles` 字段中是否包含特定角色(如 `"role_ivm_user"`),避免 `operator does not exist: json = unknown` 错误。
在 PostgreSQL 中,JSON 类型字段不支持直接使用 IN、= 或 LIKE 等标准比较操作符进行数组元素匹配——这是因为 roles 存储的是 JSON 文本(如 ["ROLE_IVM_USER"]),而非关系型数组。你原写的 DQL 条件:
->andWhere(sprintf('%s.roles IN (:roles)', $rootAlias))
->setParameter('roles', [User::ROLE_IVM_USER]);会被 Doctrine 转译为类似 WHERE roles IN ('ROLE_IVM_USER') 的 SQL,而 PostgreSQL 无法对 json 类型执行 IN 操作,故抛出 SQLSTATE[42883] 错误。
✅ 正确做法是利用 PostgreSQL 原生 JSONB 操作符(推荐将列类型升级为 JSONB,性能更优、功能更强),并结合 Doctrine 原生查询(createNativeQuery)精准匹配:
✅ 推荐方案:使用 ?? 操作符(JSONB 键存在检查)
?? 是 PostgreSQL JSONB 的专用操作符,用于判断某个字符串键(或元素值,在 jsonb 数组上下文中)是否存在。注意:它要求左操作数为 jsonb 类型,因此需显式转换:
use Doctrine\ORM\Query\ResultSetMappingBuilder;
$rsm = new ResultSetMappingBuilder($this->getEntityManager(), ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT);
$rsm->addRootEntityFromClassMetadata('App\Entity\User', 'u');
$rawSql = <<getEntityManager()->createNativeQuery($rawSql, $rsm);
$query->setParameter('role', User::ROLE_IVM_USER); // 传入字符串,如 "ROLE_IVM_USER"
return $query->getResult(); ⚠️ 注意事项:表名 "user" 使用双引号是因为 user 是 PostgreSQL 保留关键字;?? 仅适用于 jsonb,若列仍为 json 类型,请先执行迁移: ALTER TABLE "user" ALTER COLUMN roles TYPE JSONB USING roles::JSONB;若需匹配嵌套结构(如 { "roles": ["ROLE_IVM_USER"] }),应改用 @> 操作符: WHERE u.roles::jsonb @> :role_json并传参 json_encode(['ROLE_IVM_USER'])。
? 替代方案(DQL 兼容,无需原生 SQL)
若坚持使用 DQL(如需复用 QueryBuilder),可借助 Doctrine 扩展(如 doctrine/dbal-postgresql-json)或自定义 DQL 函数,但原生方案更轻量、可控性更高。
✅ 总结
| 方案 | 是否推荐 | 说明 |
|---|---|---|
| IN 直接比较 JSON 字段 | ❌ | 类型不匹配,必然报错 |
| ::jsonb ?? :role(本文方案) | ✅✅✅ | 简洁、高效、原生支持,强烈推荐 |
| @> + json_encode() | ✅ | 适合复杂嵌套结构,稍多一层序列化开销 |
掌握 jsonb 类型与 ??、@>、#> 等操作符,是驾驭 PostgreSQL JSON 数据的关键一步。务必确保数据库列类型为 JSONB,并在查询中显式转换,即可彻底规避此类类型错误。










