本文目录导读:

ThinkPHP 的查询构造器(Query Builder)功能非常强大,它提供了面向对象的方式构建 SQL 查询,避免手写 SQL 的繁琐且更安全(自动防注入),以下是 ThinkPHP 5.x/6.x 中查询构造器的高级用法
数组条件(批量): 原生字段( 注意:saveAll 实际是逐条更新,并非MySQL原生批量更新。 总结建议:
尽量使用数组条件或闭包来构建复杂查询,避免字符串拼接导致的SQL注入,在大量数据更新时,优先使用
链式操作的灵魂:常用高级方法
1 条件分支查询(
where 进阶)
// 等效于 WHERE status=1 AND name='thinkphp' AND id>10
$map = [
'status' => 1,
'name' => 'thinkphp',
'id' => ['>', 10],
// 支持模糊、区间等
'create_time' => ['between', ['2023-01-01', '2023-12-31']],
'title' => ['like', '%php%'],
'id' => ['not in', [1, 2, 3]],
];
Db::name('user')->where($map)->select();
whereRaw(原生复杂条件):// 处理复杂的OR逻辑组合
Db::name('user')
->where('status', 1)
->whereRaw('(score > 80 OR level > 3)')
->select();
whereOr / whereX:// 闭包构建分组查询
Db::name('user')->where(function ($query) {
$query->where('name', 'like', '%a%')->whereOr('nickname', 'like', '%a%');
})->where('status', 1)->select();
// 生成: WHERE (name LIKE '%a%' OR nickname LIKE '%a%') AND status = 1
2 字段与数据操作
fieldRaw):// 支持聚合函数和别名
Db::name('order')
->fieldRaw('SUM(total_amount) as total, COUNT(*) as num')
->select();
// 字段排除
Db::name('user')->withoutField('password, salt')->select();
value / column 获取单值/列:// 返回第一条记录的某个字段值
$name = Db::name('user')->where('id', 1)->value('name');
// 返回某一列的值集合(可以指定索引字段)
$names = Db::name('user')->column('name', 'id'); // 返回 [id => name]
3 排序与分组
orderRaw:// 自定义排序,按指定id顺序排序 FIELD()
Db::name('user')
->where('id', 'in', [5, 1, 3])
->orderRaw("FIELD(id, 5, 1, 3)")
->select();
group + having:Db::name('order')
->field('user_id, COUNT(*) as order_count, SUM(money) as total_money')
->group('user_id')
->having('order_count > 5 AND total_money > 1000')
->select();
4 聚合查询(高级统计)
// 平均值
$avg = Db::name('score')->where('course_id', 1)->avg('score');
// 分组统计 count 支持指定字段去重
$count = Db::name('order')->distinct(true)->count('user_id');
高阶数据操作(增删改)
1 批量更新(
saveAll / 循环)$data = [
['id' => 1, 'name' => 'a'],
['id' => 2, 'name' => 'b'],
];
Db::name('user')->saveAll($data);
2 字段自增/自减(
inc / dec)// 浏览量 +1,且只在帖子状态正常时更新
Db::name('article')
->where('id', 10)
->where('status', 1)
->inc('views') // 可以指定步长 ->inc('views', 5)
->update();
// 库存 -1
Db::name('product')->where('id', 1)->dec('stock', 1)->update();
3 表达式更新(
updateRaw / exp)// 将 price 设置成 price * 1.1(涨价10%)
Db::name('product')
->where('id', 1)
->updateRaw('price = price * 1.1');
// 或者使用 exp 表达式(TP5风格)
Db::name('product')->where('id', 1)->update([
'price' => Db::raw('price * 1.1')
]);
4 查找并更新/删除(
findOrFail 等)// 查不到则抛异常
$user = Db::name('user')->where('id', 999)->findOrFail();
// 查找并更新(不存在则不操作)
Db::name('user')->where('id', 1)->updateOrFail(['name' => 'x']);
性能与安全优化(重点)
1 使用
fetchSql 调试// 打印SQL,不真正执行
$sql = Db::name('user')->where('id', 1)->fetchSql(true)->find();
dump($sql);
2
findInSet (处理逗号分隔字段)// 假设表里有 tags 字段,值为 '1,2,3'
// 查找包含标签ID为 2 的记录(比 like 更精确,避免 12 被匹配)
Db::name('article')->whereFindInSet('tags', 2)->select();
// 或者
Db::name('article')->where('tags', 'find in set', 2)->select();
3
JSON 字段查询(MySQL 5.7+)// 假设 info 字段是JSON类型 {"age": 25, "name": "tp"}
Db::name('user')
->where('info->age', '>', 20)
->select();
// JSON 字段更新
Db::name('user')->where('id', 1)->update([
'info->age' => 30
]);
4 子查询(闭包子查询)
// 查出一批有订单的用户ID,再查这些用户的信息
$subQuery = Db::name('order')->field('user_id')->where('status', 1)->group('user_id');
$users = Db::name('user')
->where('id', 'in', $subQuery)
->select();
// 注意:TP会将闭包对象自动解析为子查询字符串
高级场景实战案例
连表查询 + 条件复杂 + 分页
list($page, $limit) = [1, 10];
$list = Db::name('order')
->alias('o') // 主表别名
->join('user u', 'u.id = o.user_id') // 关联用户
->join('product p', 'p.id = o.product_id', 'LEFT') // 左连接商品
->where('o.status', 1)
->where(function ($query) {
// 关键字搜索,多字段模糊
$keyword = input('keyword');
if ($keyword) {
$query->where('u.name', 'like', "%$keyword%")
->whereOr('p.title', 'like', "%$keyword%");
}
})
->field('o.*, u.name as uname, p.title as ptitle')
->order('o.create_time DESC')
->page($page, $limit)
->select();
事务+锁
Db::transaction(function () {
// 悲观锁:锁定该行,防止并发超卖
$product = Db::name('product')
->where('id', 1)
->lock(true) // 加 FOR UPDATE
->find();
if ($product['stock'] <= 0) {
throw new \Exception('库存不足');
}
Db::name('product')->where('id', 1)->dec('stock')->update();
Db::name('log')->insert(['msg' => '扣减库存']);
});
版本差异提醒(TP6 变化)
Db::name() 静态调用需引入 use think\facade\Db;。whereOr 闭包:在 TP6 中,where 闭包内需要用 $query->whereOr(...),写法基本一致。SoftDelete 特性,但查询构造器本身不包含该功能,需结合模型使用。inc/dec 和 whereRaw 做原子操作,能有效避免并发问题。