在Laravel项目开发中,经常会遇到字段存储为JSON数组的场景,比如用户表的tags字段存储用户的所有标签,订单表的attr字段存储商品属性集合,此时需要筛选出至少包含某个指定值的记录,就需要用到针对性的查询方法。
核心查询方法:whereJsonContains
Laravel的Eloquent查询构造器原生提供了whereJsonContains方法,专门用于JSON数组列的包含判断,该方法会自动适配不同数据库的语法,不需要开发者手动拼接原生SQL,是最推荐的用法。
基础用法示例
假设用户表users的tags字段是JSON数组格式,存储用户的标签,现在要查询所有包含标签「php」的用户,代码如下:
<?php
// 查询tags数组中包含php的记录
$users = AppModelsUser::whereJsonContains('tags', 'php')->get();
// 如果需要同时包含多个值,可以传递数组作为第二个参数
// 以下查询tags中同时包含php和laravel的记录
$users = AppModelsUser::whereJsonContains('tags', ['php', 'laravel'])->get();
不同数据库的支持情况
whereJsonContains方法对不同数据库的适配规则如下:
- MySQL 5.7+、MariaDB 10.2+:原生支持JSON数组包含判断,生成的SQL会调用
JSON_CONTAINS函数 - PostgreSQL:使用
@>操作符判断数组包含关系 - SQL Server 2016+:使用
JSON_VALUE结合OPENJSON实现判断
如果需要查看生成的具体SQL语句,可以通过toSql方法输出:
<?php
$sql = AppModelsUser::whereJsonContains('tags', 'php')->toSql();
// MySQL下生成的SQL类似:select * from `users` where json_contains(`tags`, ?)
其他实现方式的对比
手动拼接原生SQL
部分开发者会直接写原生SQL实现过滤,比如MySQL下使用JSON_CONTAINS函数:
<?php
$users = AppModelsUser::whereRaw("JSON_CONTAINS(tags, '" . json_encode('php') . "')")->get();
这种方式的问题在于:第一,需要手动处理参数转义,容易出现SQL注入风险;第二,不兼容其他数据库,如果项目后续切换数据库类型,需要修改所有相关查询代码,维护成本高。
使用like模糊匹配
还有开发者会用where结合like做模糊查询:
<?php
$users = AppModelsUser::where('tags', 'like', '%"php"%')->get();
这种方式存在明显缺陷:如果JSON数组中有「php_framework」这样的标签,也会被错误匹配到,而且无法判断数组结构,查询准确性低,同时like匹配无法使用JSON字段的索引,大数据量下查询性能很差,不推荐使用。
性能优化建议
如果JSON数组列的查询频率很高,建议给该列添加对应的索引提升查询速度:
- MySQL 8.0+支持给JSON数组列添加多值索引,创建语法如下:
<?php
// 迁移文件中添加多值索引
Schema::table('users', function (Blueprint $table) {
$table->index(['tags'], 'tags_index')->algorithm('btree');
// 或者使用JSON数组专用索引
$table->rawIndex('(CAST(tags AS CHAR(255) ARRAY))', 'tags_array_index');
});
- PostgreSQL可以直接给JSONB类型的列创建GIN索引,查询效率会大幅提升
另外需要注意,whereJsonContains方法如果传入的第二个参数是数组,表示同时满足多个包含条件,如果需要满足任意一个值即可,需要链式调用多个whereJsonContains或者用orWhereJsonContains:
<?php
// 查询tags中包含php或者包含laravel的用户
$users = AppModelsUser::where(function ($query) {
$query->whereJsonContains('tags', 'php')
->orWhereJsonContains('tags', 'laravel');
})->get();
常见注意事项
第一,确认字段的存储格式是正确的JSON数组,如果存储的是JSON对象或者字符串,whereJsonContains方法会返回空结果,可以在查询前通过cast方法定义字段类型:
<?php
// User模型中定义
protected $casts = [
'tags' => 'array',
];
第二,如果JSON数组中的元素是数字类型,查询时第二个参数要传数字而不是字符串,比如查询包含数字1的标签:
<?php
$users = AppModelsUser::whereJsonContains('tags', 1)->get();
第三,部分旧版本数据库不支持JSON数组查询,此时建议将JSON数组拆分为关联表存储,避免使用JSON格式存储需要频繁查询的数组数据,从数据结构层面提升查询效率。
LaravelJSON数组列查询whereJsonContains数据库查询优化Eloquent修改时间:2026-06-11 22:48:37