数据库查询构建器
DuxLite 基于 Laravel Eloquent 查询构建器,提供了丰富而强大的数据库查询接口。查询构建器使用流畅的接口来构建和运行数据库查询,为各种数据库操作提供了方便的、语义化的接口。
核心概念
获取查询构建器
IDE 代码提示
重要提示:在 DuxLite 中,使用模型查询时建议使用 query()
方法而不是静态调用,这样可以获得完整的 IDE 代码提示和类型检查。
php
use Core\Database\Model;
class User extends Model
{
protected $table = 'users';
protected $tableComment = '用户表';
}
// ✅ 推荐方式:使用 query() 方法获得完整代码提示
$users = User::query()->where('status', 1)->get();
$user = User::query()->find(1);
// ❌ 不推荐:静态调用可能缺少 IDE 提示
$users = User::where('status', 1)->get();
在资源控制器中使用
DuxLite 资源控制器通过 queryModel()
方法自动使用 query()
方法:
php
use Core\Resources\Action\Resources;
use Illuminate\Database\Eloquent\Builder;
class UserController extends Resources
{
protected string $model = User::class;
/**
* queryModel 方法自动调用 query()
*/
public function queryMany(Builder $query, ServerRequestInterface $request, array $args): void
{
// $query 已经是 Builder 实例,具有完整的代码提示
$query->where('status', 1)
->orderBy('created_at', 'desc')
->with('profile');
}
}
基础查询
检索数据
php
// 获取所有记录
$users = User::query()->get();
// 获取单条记录
$user = User::query()->first();
$user = User::query()->find(1);
$user = User::query()->where('email', $email)->first();
// 获取指定列
$users = User::query()->select(['id', 'name', 'email'])->get();
$users = User::query()->select('id', 'name', 'email')->get();
// 获取不重复的记录
$users = User::query()->distinct()->get();
// 获取单个值
$name = User::query()->where('id', 1)->value('name');
$names = User::query()->pluck('name');
$namesByEmail = User::query()->pluck('name', 'email');
聚合查询
php
// 统计函数
$count = User::query()->count();
$max = User::query()->max('created_at');
$min = User::query()->min('created_at');
$avg = User::query()->avg('score');
$sum = User::query()->sum('points');
// 条件聚合
$activeCount = User::query()->where('status', 1)->count();
$avgScore = User::query()->where('status', 1)->avg('score');
// 检查记录是否存在
$exists = User::query()->where('email', $email)->exists();
$notExists = User::query()->where('email', $email)->doesntExist();
条件查询
Where 子句
php
// 基础 where 条件
$users = User::query()->where('status', 1)->get();
$users = User::query()->where('votes', '>', 100)->get();
$users = User::query()->where('name', 'like', '%john%')->get();
// 多个 where 条件
$users = User::query()
->where('status', 1)
->where('votes', '>', 100)
->get();
// where 数组条件
$users = User::query()->where([
['status', '=', 1],
['votes', '>', 100],
])->get();
// orWhere 条件
$users = User::query()
->where('votes', '>', 100)
->orWhere('name', 'John')
->get();
// whereBetween / orWhereBetween
$users = User::query()->whereBetween('votes', [1, 100])->get();
$users = User::query()->whereNotBetween('votes', [1, 100])->get();
// whereIn / whereNotIn
$users = User::query()->whereIn('id', [1, 2, 3])->get();
$users = User::query()->whereNotIn('id', [1, 2, 3])->get();
// whereNull / whereNotNull
$users = User::query()->whereNull('updated_at')->get();
$users = User::query()->whereNotNull('updated_at')->get();
// whereDate / whereTime
$users = User::query()->whereDate('created_at', '2023-12-25')->get();
$users = User::query()->whereYear('created_at', 2023)->get();
$users = User::query()->whereMonth('created_at', 12)->get();
$users = User::query()->whereDay('created_at', 25)->get();
$users = User::query()->whereTime('created_at', '=', '11:20:45')->get();
高级 Where 子句
php
// 参数分组
$users = User::query()
->where('name', '=', 'John')
->where(function (Builder $query) {
$query->where('votes', '>', 100)
->orWhere('title', '=', 'Admin');
})
->get();
// JSON Where 子句 (MySQL 5.7+)
$users = User::query()->where('preferences->dining->meal', 'salad')->get();
$users = User::query()->whereJsonContains('options->languages', 'en')->get();
$users = User::query()->whereJsonLength('options->languages', 3)->get();
// 列比较
$users = User::query()->whereColumn('first_name', 'last_name')->get();
$users = User::query()->whereColumn('updated_at', '>', 'created_at')->get();
$users = User::query()->whereColumn([
['first_name', '=', 'last_name'],
['updated_at', '>', 'created_at'],
])->get();
条件查询
php
// when 条件查询
$sortBy = request()->input('sort_by');
$users = User::query()
->when($sortBy, function (Builder $query, string $sortBy) {
return $query->orderBy($sortBy);
})
->get();
// unless 条件查询
$users = User::query()
->unless(empty($search), function (Builder $query) use ($search) {
return $query->where('name', 'like', "%{$search}%");
})
->get();
排序与分组
排序
php
// 基础排序
$users = User::query()->orderBy('name')->get();
$users = User::query()->orderBy('name', 'desc')->get();
// 多字段排序
$users = User::query()
->orderBy('name', 'asc')
->orderBy('email', 'desc')
->get();
// 最新和最旧
$users = User::query()->latest()->get(); // 按 created_at desc
$users = User::query()->latest('updated_at')->get(); // 按 updated_at desc
$users = User::query()->oldest()->get(); // 按 created_at asc
// 随机排序
$users = User::query()->inRandomOrder()->get();
// 重新排序
$query = User::query()->orderBy('name');
$users = $query->reorder()->get(); // 移除所有排序
$users = $query->reorder('email', 'desc')->get(); // 重新设置排序
// 原生排序
$users = User::query()->orderByRaw('updated_at - created_at DESC')->get();
分组
php
// 基础分组
$users = User::query()
->select('status', User::query()->raw('count(*) as total'))
->groupBy('status')
->get();
// 多字段分组
$users = User::query()
->select('status', 'type', User::query()->raw('count(*) as total'))
->groupBy('status', 'type')
->get();
// Having 子句
$users = User::query()
->select('status', User::query()->raw('count(*) as total'))
->groupBy('status')
->having('total', '>', 1)
->get();
// Having 原生条件
$users = User::query()
->select('status', User::query()->raw('count(*) as total'))
->groupBy('status')
->havingRaw('count(*) > 1')
->get();
限制和偏移
基础限制
php
// 限制结果数量
$users = User::query()->take(5)->get();
$users = User::query()->limit(5)->get();
// 跳过记录
$users = User::query()->skip(10)->take(5)->get();
$users = User::query()->offset(10)->limit(5)->get();
// 分页
$users = User::query()->forPage(3, 15)->get(); // 第3页,每页15条
游标和分块
php
// 分块处理大量数据
User::query()->chunk(200, function (Collection $users) {
foreach ($users as $user) {
// 处理每个用户
}
});
// 按 ID 分块
User::query()->chunkById(200, function (Collection $users) {
foreach ($users as $user) {
// 处理每个用户
}
});
// 懒加载集合
foreach (User::query()->lazy() as $user) {
// 处理用户,内存友好
}
// 懒加载分块
foreach (User::query()->lazyById(200) as $user) {
// 分块懒加载
}
// 游标分页
foreach (User::query()->cursor() as $user) {
// 使用游标遍历
}
连接查询
基础连接
php
// 内连接
$users = User::query()
->join('posts', 'users.id', '=', 'posts.user_id')
->select('users.*', 'posts.title')
->get();
// 左连接
$users = User::query()
->leftJoin('posts', 'users.id', '=', 'posts.user_id')
->select('users.*', 'posts.title')
->get();
// 右连接
$users = User::query()
->rightJoin('posts', 'users.id', '=', 'posts.user_id')
->select('users.*', 'posts.title')
->get();
// 交叉连接
$sizes = User::query()
->crossJoin('colors')
->get();
高级连接
php
// 复杂连接条件
$users = User::query()
->join('posts', function (JoinClause $join) {
$join->on('users.id', '=', 'posts.user_id')
->where('posts.status', '=', 'published');
})
->get();
// 子查询连接
$latestPosts = Post::query()
->select('user_id', User::query()->raw('MAX(created_at) as last_post_created_at'))
->where('status', 'published')
->groupBy('user_id');
$users = User::query()
->joinSub($latestPosts, 'latest_posts', function (JoinClause $join) {
$join->on('users.id', '=', 'latest_posts.user_id');
})
->get();
联合查询
php
// 基础联合
$first = User::query()->where('votes', '>', 100);
$second = User::query()->where('status', 'premium');
$users = $first->union($second)->get();
// 联合所有
$users = $first->unionAll($second)->get();
原生表达式
Raw 方法
php
// 原生选择
$users = User::query()
->select(User::query()->raw('count(*) as user_count, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();
// 原生 Where 子句
$users = User::query()
->whereRaw('price > IF(state = "TX", ?, 100)', [200])
->get();
// 原生 Having 子句
$users = User::query()
->select('department', User::query()->raw('SUM(price) as total_sales'))
->groupBy('department')
->havingRaw('SUM(price) > ?', [2500])
->get();
// 原生排序
$users = User::query()
->orderByRaw('updated_at - created_at DESC')
->get();
// 原生分组
$users = User::query()
->select('city', 'state')
->groupByRaw('city, state')
->get();
DB Raw
php
use Core\App;
// 使用 DB Raw
$users = User::query()
->select('*')
->where('votes', '>', App::db()->raw('(SELECT AVG(votes) FROM users)'))
->get();
子查询
Where 子查询
php
// Where Exists
$users = User::query()
->whereExists(function (Builder $query) {
$query->select(User::query()->raw(1))
->from('posts')
->whereColumn('posts.user_id', 'users.id');
})
->get();
// Where Not Exists
$users = User::query()
->whereNotExists(function (Builder $query) {
$query->select(User::query()->raw(1))
->from('posts')
->whereColumn('posts.user_id', 'users.id');
})
->get();
// 子查询 Where 子句
$users = User::query()
->where('votes', '>', function (Builder $query) {
$query->select(User::query()->raw('AVG(votes)'))
->from('users')
->where('status', 'active');
})
->get();
Select 子查询
php
// 添加子查询列
$users = User::query()
->addSelect([
'last_post_created_at' => Post::query()
->select('created_at')
->whereColumn('user_id', 'users.id')
->orderBy('created_at', 'desc')
->limit(1)
])
->get();
// 使用子查询排序
$users = User::query()
->orderBy(
Post::query()
->select('created_at')
->whereColumn('user_id', 'users.id')
->orderBy('created_at', 'desc')
->limit(1),
'desc'
)
->get();
在资源控制器中的应用
基础查询定制
php
use Core\Resources\Action\Resources;
use Core\Resources\Attribute\Resource;
use Illuminate\Database\Eloquent\Builder;
#[Resource(
app: "admin",
route: "/users",
name: "user"
)]
class UserController extends Resources
{
protected string $model = User::class;
/**
* 多条数据查询定制
*/
public function queryMany(Builder $query, ServerRequestInterface $request, array $args): void
{
$params = $request->getQueryParams();
// 状态筛选
if (isset($params['status'])) {
$query->where('status', $params['status']);
}
// 关键词搜索
if (!empty($params['search'])) {
$query->where(function (Builder $q) use ($params) {
$q->where('name', 'like', "%{$params['search']}%")
->orWhere('email', 'like', "%{$params['search']}%");
});
}
// 日期范围筛选
if (!empty($params['start_date'])) {
$query->where('created_at', '>=', $params['start_date']);
}
if (!empty($params['end_date'])) {
$query->where('created_at', '<=', $params['end_date']);
}
// 预加载关联
$query->with(['profile', 'roles']);
}
/**
* 单条数据查询定制
*/
public function queryOne(Builder $query, ServerRequestInterface $request, array $args): void
{
// 预加载详细关联
$query->with([
'profile',
'posts' => function (Builder $q) {
$q->where('status', 'published')->latest();
},
'roles.permissions'
]);
}
/**
* 通用查询条件
*/
public function query(Builder $query): void
{
// 只查询活跃用户
$query->where('status', 'active');
}
}
高级查询示例
php
class PostController extends Resources
{
protected string $model = Post::class;
public function queryMany(Builder $query, ServerRequestInterface $request, array $args): void
{
$params = $request->getQueryParams();
// 复杂的搜索逻辑
if (!empty($params['keyword'])) {
$query->where(function (Builder $q) use ($params) {
$q->where('title', 'like', "%{$params['keyword']}%")
->orWhere('content', 'like', "%{$params['keyword']}%")
->orWhere('excerpt', 'like', "%{$params['keyword']}%");
});
}
// 根据作者筛选
if (!empty($params['author_id'])) {
$query->where('user_id', $params['author_id']);
}
// 根据分类筛选
if (!empty($params['category_id'])) {
$query->where('category_id', $params['category_id']);
}
// 根据标签筛选
if (!empty($params['tag'])) {
$query->whereHas('tags', function (Builder $q) use ($params) {
$q->where('name', $params['tag']);
});
}
// 根据评论数筛选
if (!empty($params['min_comments'])) {
$query->has('comments', '>=', (int)$params['min_comments']);
}
// 统计信息
$query->withCount(['comments', 'likes']);
// 添加评分子查询
$query->addSelect([
'avg_rating' => Rating::query()
->select(User::query()->raw('AVG(rating)'))
->whereColumn('post_id', 'posts.id')
]);
// 预加载关联
$query->with([
'author:id,name,email',
'category:id,name,slug',
'tags:id,name'
]);
// 排序逻辑
switch ($params['sort'] ?? 'latest') {
case 'popular':
$query->orderBy('views', 'desc');
break;
case 'commented':
$query->orderBy('comments_count', 'desc');
break;
case 'rating':
$query->orderBy('avg_rating', 'desc');
break;
default:
$query->latest();
}
}
}
自定义查询作用域
php
class User extends Model
{
/**
* 活跃用户作用域
*/
public function scopeActive(Builder $query): Builder
{
return $query->where('status', 'active')
->where('email_verified_at', '!=', null);
}
/**
* 最近注册用户作用域
*/
public function scopeRecent(Builder $query, int $days = 30): Builder
{
return $query->where('created_at', '>', now()->subDays($days));
}
/**
* 高质量用户作用域
*/
public function scopeHighQuality(Builder $query): Builder
{
return $query->where('reputation', '>', 100)
->has('posts', '>=', 5);
}
/**
* 搜索作用域
*/
public function scopeSearch(Builder $query, string $keyword): Builder
{
return $query->where(function (Builder $q) use ($keyword) {
$q->where('name', 'like', "%{$keyword}%")
->orWhere('email', 'like', "%{$keyword}%")
->orWhere('bio', 'like', "%{$keyword}%");
});
}
}
// 在控制器中使用作用域
class UserController extends Resources
{
public function queryMany(Builder $query, ServerRequestInterface $request, array $args): void
{
$params = $request->getQueryParams();
// 使用作用域
$query->active();
if (!empty($params['recent'])) {
$query->recent($params['recent']);
}
if (!empty($params['high_quality'])) {
$query->highQuality();
}
if (!empty($params['search'])) {
$query->search($params['search']);
}
}
}
查询调试
调试 SQL
php
use Core\App;
// 开启查询日志
App::db()->enableQueryLog();
// 执行查询
$users = User::query()->where('votes', '>', 100)->get();
// 获取查询日志
$queries = App::db()->getQueryLog();
foreach ($queries as $query) {
dump($query['query'], $query['bindings'], $query['time']);
}
// 获取 SQL 语句(不执行)
$sql = User::query()->where('votes', '>', 100)->toSql();
dump($sql); // select * from `users` where `votes` > ?
// 获取带绑定参数的 SQL
$query = User::query()->where('votes', '>', 100);
$sqlWithBindings = str_replace_array(
'?',
$query->getBindings(),
$query->toSql()
);
dump($sqlWithBindings);
性能分析
php
// 使用 explain 分析查询
$result = App::db()->select('explain ' .
User::query()->where('email', $email)->toSql(),
[$email]
);
// 查询执行计划
$users = User::query()
->where('status', 'active')
->where('created_at', '>', now()->subDays(30))
->explain();
性能优化技巧
查询优化
php
// ✅ 好的做法
class UserController extends Resources
{
public function queryMany(Builder $query, ServerRequestInterface $request, array $args): void
{
// 1. 只选择需要的字段
$query->select(['id', 'name', 'email', 'created_at']);
// 2. 合理使用索引
$query->where('status', 'active') // 确保 status 有索引
->where('created_at', '>', now()->subDays(30)); // 确保 created_at 有索引
// 3. 预加载关联以避免 N+1 问题
$query->with([
'profile:user_id,avatar,bio',
'roles:id,name'
]);
// 4. 使用计数而不是加载数据
$query->withCount(['posts', 'comments']);
// 5. 合理的分页
// 由框架自动处理
}
}
// ❌ 避免的做法
class BadUserController extends Resources
{
public function queryMany(Builder $query, ServerRequestInterface $request, array $args): void
{
// 获取所有字段
$query->select('*');
// 没有使用索引的条件
$query->where(User::query()->raw('UPPER(name)'), 'JOHN');
// 过度预加载
$query->with('posts.comments.user.profile.avatar');
// 在应用层处理数据
$query->get()->filter(function ($user) {
return $user->posts->count() > 5;
});
}
}
批量操作优化
php
// 批量插入
$userData = [
['name' => 'John', 'email' => 'john@example.com'],
['name' => 'Jane', 'email' => 'jane@example.com'],
// ... 更多数据
];
User::query()->insert($userData);
// 批量更新
User::query()->where('status', 'pending')->update(['status' => 'active']);
// 使用 upsert(MySQL 5.7+)
User::query()->upsert(
[
['id' => 1, 'name' => 'John', 'email' => 'john@example.com'],
['id' => 2, 'name' => 'Jane', 'email' => 'jane@example.com'],
],
['id'], // 唯一标识符
['name', 'email'] // 要更新的字段
);
最佳实践
查询构建规范
php
// 1. 使用 query() 方法获得代码提示
$users = User::query()->where('status', 1)->get();
// 2. 合理的方法链顺序
$users = User::query()
->select(['id', 'name', 'email']) // 选择字段
->where('status', 'active') // 条件筛选
->whereHas('posts', function($q) { // 关联条件
$q->where('published', true);
})
->with(['profile', 'roles']) // 预加载
->withCount('posts') // 计数
->orderBy('created_at', 'desc') // 排序
->limit(20) // 限制
->get(); // 执行
// 3. 复杂条件使用闭包分组
$users = User::query()
->where('status', 'active')
->where(function (Builder $query) use ($search) {
$query->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
})
->get();
安全注意事项
php
// ✅ 使用参数绑定
$users = User::query()->whereRaw('age > ? AND city = ?', [25, $city])->get();
// ❌ 避免 SQL 注入
$users = User::query()->whereRaw("age > {$age} AND city = '{$city}'")->get();
// ✅ 验证用户输入
$allowedSorts = ['name', 'email', 'created_at'];
$sortField = in_array($request->input('sort'), $allowedSorts)
? $request->input('sort')
: 'created_at';
$users = User::query()->orderBy($sortField)->get();
DuxLite 的查询构建器继承了 Laravel Eloquent 的所有强大功能,通过使用 query()
方法,你可以获得完整的 IDE 支持,构建出高效、安全、可维护的数据库查询。