🔍 查询助手
QueryHelper 是 ThinkAdmin 提供的数据查询助手,旨在简化数据查询及分页操作。通过该功能,开发者只需输入相关变量,系统便能自动组装数据搜索条件,快速获取所需数据。
📚 基础概念
🤔 基本介绍
QueryHelper(查询助手)是 ThinkAdmin 框架提供的一个工具类,用于简化数据库查询操作。
简单理解:就像 SQL 查询的"快捷方式",帮你自动处理常见的查询逻辑,无需手动编写复杂的 SQL 语句。
传统方式(不推荐):
// ❌ 传统方式:需要手动处理分页、搜索、排序等
$page = input('page', 1);
$limit = 20;
$offset = ($page - 1) * $limit;
$where = [];
if (input('username')) {
$where[] = ['username', 'like', '%' . input('username') . '%'];
}
if (input('status')) {
$where[] = ['status', '=', input('status')];
}
$list = SystemUser::where($where)
->order('id desc')
->limit($limit)
->page($page)
->select();
$total = SystemUser::where($where)->count();
$pageHtml = buildPageHtml($total, $limit, $page);使用 QueryHelper(推荐):
// ✅ 使用 QueryHelper:自动处理分页、搜索、排序
SystemUser::mQuery()
->like('username')
->equal('status')
->order('id desc')
->page();🤔 使用优势
问题1:代码重复
不使用 QueryHelper 时,每个列表页面都需要重复编写分页、搜索、排序等逻辑:
// ❌ 每个列表页面都要写这些代码
$page = input('page', 1);
$limit = 20;
$where = [];
// ... 大量重复代码问题2:容易出错
手动处理分页、搜索等逻辑容易出错:
// ❌ 容易出错:分页计算错误、通配符拼接不符合预期等
$offset = ($page - 1) * $limit; // 如果 $page 为 0 会出错
$where[] = ['username', 'like', input('username')]; // 未拼接通配符,可能无法得到期望的模糊搜索问题3:维护困难
当需要修改查询逻辑时,需要在多个地方修改:
// ❌ 需要在多个控制器中修改相同的逻辑
// UserController.php
$where[] = ['status', '=', 1];
// OrderController.php
$where[] = ['status', '=', 1]; // 重复代码使用 QueryHelper 的优势:
// ✅ 统一接口,代码简洁
SystemUser::mQuery()->equal('status')->page();
SystemOrder::mQuery()->equal('status')->page();🎯 核心功能
功能1:自动组装查询条件
根据 URL 参数或表单数据自动生成 WHERE 条件:
// URL: /admin/user/index?username=admin&status=1
SystemUser::mQuery()
->like('username') // 自动生成:WHERE username LIKE '%admin%'
->equal('status') // 自动生成:AND status = 1
->page();功能2:分页处理
自动处理分页逻辑,无需手动计算:
// 自动处理分页,无需手动计算 offset、limit
SystemUser::mQuery()->page();
// 自动生成分页 HTML,无需手动编写功能3:数据搜索
支持模糊搜索、精确匹配、范围查询等:
SystemUser::mQuery()
->like('username,nickname') // 模糊搜索
->equal('status') // 精确匹配
->dateBetween('create_at') // 日期范围查询
->page();功能4:调用 ThinkORM 查询能力
QueryHelper 未重新实现关联查询,未识别的链式方法会转发给内部 ThinkORM Query 对象,因此可以继续使用 with()、field()、join()、count() 等 ThinkORM 能力:
SystemUser::mQuery()
->with(['userinfo' => function ($query) {
$query->field('code,name');
}])
->page();功能5:数据排序
支持多字段排序:
SystemUser::mQuery()
->order('status desc, create_at desc, id asc')
->page();功能6:数据过滤
支持数据过滤和二次处理:
// 在控制器中定义回调方法
protected function _page_filter(array &$data)
{
foreach ($data as &$vo) {
$vo['status_text'] = $vo['status'] ? '启用' : '禁用';
}
}📖 相关概念
模型(Model)
- 数据模型,对应数据库表
- 用于操作数据库
- 例如:
SystemUser对应system_user表
控制器(Controller)
- 处理用户请求
- 调用模型获取数据
- 返回响应
链式操作
- 可以连续调用多个方法
- 例如:
$query->where()->like()->order()->page()
分页
- 将大量数据分成多个页面显示
- 每页显示固定数量的数据
- 例如:每页 20 条,共 100 条数据,分为 5 页
查询条件
- 用于筛选数据的条件
- 例如:
WHERE status = 1
✨ 主要特性
- 自动查询条件组装: 根据输入参数自动生成查询条件
- 分页支持: 内置分页功能,支持多种分页模式
- 链式操作: 支持链式调用,代码简洁易读
- 模板输出: 支持直接输出到模板,减少重复代码
- 输入读取: 根据指定输入源读取 GET、POST、REQUEST 或数组参数
- 查询代理: 未识别的方法会转发给 ThinkORM
Query对象 - Layui 表格: 支持按
get.layui.table输出 Layui 表格数据 - 分页过滤: 支持
_page_filter回调二次处理列表数据
📋 使用场景
- 后台数据列表展示
- 数据搜索和筛选
- 分页数据查询
- 数据导出功能
创建查询实例
创建快捷查询助手目前有两种方式,最为方便快捷的方式为模型创建法。
若在模型中需要使用到 QueryHelper 快捷查询助手,模型需要继承 \think\admin\Model 类;
若在控制器需要使用到 QueryHelper 快捷查询助手,控制器需要继承 \think\admin\Controller 类;
// 1. 控制器中,创建查询器(查询器支持链式操作)
// 控制器需要继承 \think\admin\Controller 类;
// 参数:$dbQuery 数据模型、查询对象或者数据表名
// 参数:$input 为输入对象,如:post、get、request 或 数组
$query = $this->_query($dbQuery, $input);
// 2. 新版本中,模型也支持直接创建查询器(推荐方式)
// 模型需要继承基础模型 \think\admin\Model
// 参数:$input 为输入对象,如:post、get、request 或 数组
// 参数:$callable 为回调用处理,回调参数1为对象本身`QueryHelper`对象
$query = SystemUser::mQuery();
$query = SystemUser::mQuery($data, function(QueryHelper $helper){});
$query = SystemUser::mQuery('post', function(QueryHelper $helper){});
$query = SystemUser::mQuery(null, function(QueryHelper $helper){
$helper->with("数据模型关联");
$helper->where(['is_deleted' => 0]);
// $helper->like()->equal() ...
});原生表格输出
调用 page 方法后,启用输出模板时会使用 HttpResponseException 响应直接输出机制,后续代码不会再操作。
对象快捷查询进行分页并输出时,模板中会得到 $list 和 $pagehtml 两个常用变量,其中 $list 为当前页数据列表数组,$pagehtml 为当前分页导航条。若调用 page(true, false),则不会渲染模板,而是返回 ['page' => ..., 'list' => ...] 结构;若调用 page(false, false),则不分页且不渲染模板,返回 ['list' => ...] 结构。 前端 html -> table 可以通过使用 foreach 循环输出 Table 数据,生成原生 table 结构的标签代码。
// 快捷查询助手分页参数:
// 参数:$page bool|int 是否启用分页,true 为分页,false 为不分页,数字表示指定页码
// 参数:$display bool 是否直接显示模板,默认显示模板;false 时返回数据数组
// 参数:$total bool|int 分页记录数,false 自动统计,true 表示使用快速分页模式,数字表示指定总数
// 参数:$limit int 每页数量,分页时每页数据记录数量
// 参数:$template string 模板名称,当启用显示模板时,会加载指定模板
$query->page($page, $display, $total, $limit, $template);
// 常用用法:
$query->page(); // 分页并显示模板
$query->page(true, false); // 分页但不显示模板,返回数据数组(用于API接口)
$query->page(false); // 不分页,但仍会按默认行为显示模板
$query->page(false, false); // 不分页且不显示模板,返回所有数据数组
$query->page(2, false, false, 20); // 查询第 2 页,每页 20 条,返回数据数组layTable 输出
新版本推荐使用 layui.table 组件显示表格数据,目前有针对 Layui 表格组件进行封装 layTable 插件,可动态填充页面高度,支持搜索表单绑定及混合模式调度。
<div class="think-box-shadow">
<table id="OplogData" data-url="{:sysuri()}" data-target-search="form.form-search"></table>
</div>
<script>
$(function () {
// 动态创建 layui.table 表格
$('#OplogData').layTable({
even: true, height: 'full',
sort: {field: 'id', type: 'desc'},
cols: [[
{checkbox: true},
{field: 'id', title: 'ID', width: 80, sort: true, align: 'center'},
{field: 'username', title: '操作账号', minWidth: 100, sort: true, align: 'center'},
{field: 'node', title: '操作节点', minWidth: 120},
{field: 'action', title: '操作行为', minWidth: 120},
{field: 'content', title: '操作描述', minWidth: 120},
{field: 'geoip', title: '访问地址', minWidth: 100},
{field: 'geoisp', title: '网络服务商', minWidth: 100},
{field: 'create_at', title: '操作时间', minWidth: 170, align: 'center', sort: true},
{toolbar: '#toolbar', title: '操作面板', align: 'center', fixed: 'right'}
]]
});
// 监听原 layui.table 的 tool 事件
$('#OplogData').trigger('tool', function (item) {
// 对应 layui.table 写法 layui.table.on('tool(OplogData)',function(){ })
if (item.event === 'EventName') {
// 判断 lay-event 名称处理
}
})
// 监听原 layui.table 的 toolbar 事件
$('#OplogData').trigger('toolbar', function (item) {
// 对应 layui.table 写法 layui.table.on('toolbar(OplogData)',function(){ })
})
// 以上为 layTable 兼容 layui.table 用法
// 更多事件绑定与 layui.table 的事件对应 https://layui.dev/docs/2.8/table/#table.on
// 原有 jQuery 写法需要使用 off + on 的方式实现 Element 对象查找
// 由于后台是单页程序,绑定事件需要先 off 再 on,否则容易重复绑定事件或无法触发事件
$('body').off('click', 'jQ选择器').on('click', 'jQ选择器', function () {
// 点击事件,可以用 this 读取 Element 对象属性内容
})
});
</script>
<script type="text/html" id="toolbar">
<!--{if auth('remove')}-->
<a data-action='{:url("remove")}' data-value="id#{{d.id}}" data-confirm="确认要删除这条记录吗?" class="layui-btn layui-btn-sm layui-btn-danger">删 除</a>
<!--{/if}-->
</script>后端 QueryHelper 实现表格数据自动处理。普通页面请求时会执行第一个回调并渲染模板,数据请求(get.json 或 get.layui.table)时会执行第二个回调并输出数据。
调用 layTable 方法后,普通页面请求会渲染模板;get.json 请求会走 PageHelper::init() 输出 success('JSON-DATA', $result);get.layui.table 请求会输出 Layui 表格需要的 msg/code/count/data 结构。数据请求会通过 HttpResponseException 直接响应,后续控制器代码不会再继续执行。
实际案例:系统用户管理列表
<?php
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemBase;
use think\admin\model\SystemUser;
class User extends Controller
{
public function index()
{
$this->type = $this->get['type'] ?? 'index';
SystemUser::mQuery()->layTable(function () {
// 前置操作:设置模板变量
$this->title = '系统用户管理';
$this->bases = SystemBase::items('身份权限');
}, function (QueryHelper $query) {
// 后置操作:设置查询条件
$query->where(['is_deleted' => 0, 'status' => intval($this->type === 'index')]);
// 关联查询用户身份资料
$query->with(['userinfo' => static function ($query) {
$query->field('code,name,content');
}]);
// 数据列表搜索过滤
$query->equal('status,usertype')->dateBetween('login_at,create_at');
// 支持字段别名映射;复杂组合字段以 ThinkORM 条件语法为准
$query->like('username#username,contact_phone#phone,contact_mail#mail');
});
}
}基础用法:
// QueryHelper 对象支持 Layui 表格数据加载
$query->layTable(function(QueryHelper $query){
// 普通页面请求执行,处理 HTML 模板变量
}, function(QueryHelper $query){
// 数据请求执行,处理表格查询条件与关联加载
});查询终态操作
特别注意: 下面操作的查询对象 QueryHelper 对象并不是 ThinkORM 的 Query 对象,如果获取结果操作,需要从 QueryHelper 中获取 ThinkORM 的 Query 对象。
// 1. 从 QueryHelper 对象中获取 ThinkORM 的 Query 对象
$db = $query->db(); // 获取 ThinkORM 的查询对象
// 2. 使用 ThinkORM 的 Query 对象进行操作
$count = $db->count(); // 获取记录数量
$total = $db->sum('字段'); // 统计字段的求和
$mysql = $db->buildSql(); // 获取生成的 SQL 查询语句
// 3. 更新多数据聚合及查询请查询 ThinkORM 文档查询条件处理
QueryHelper 提供了多种查询条件方法,可以根据 URL 参数、表单数据或传入数组自动生成查询条件。初始化未指定输入源时,默认读取当前 request 参数。
条件方法说明:
like():模糊查询,用于搜索文本字段equal():精确匹配,用于精确查找in():IN 查询,将输入值按分隔符拆分为数组valueRange():两字段范围查询,常用于判断输入值是否落在记录的起止字段之间valueBetween():普通范围查询,将输入值按分隔符拆分为开始值和结束值dateBetween():日期范围查询,用于查询指定日期范围的数据timeBetween():时间戳范围查询,将日期字符串转为时间戳后查询
where()、whereIn()、whereBetween() 等方法来自 ThinkORM,QueryHelper 会通过内部 Query 对象代理调用。它们不会自动读取当前请求参数,需要手动传入条件值。
参数格式说明:
- 单个字段:
$query->like('username')- 查询username字段 - 多个字段:
$query->like('username,nickname')- 分别读取同名输入参数并追加各自字段条件,不是用一个关键词同时搜索多个字段 - 别名映射:
$query->like('username#name')- URL 参数name对应数据库字段username - 复杂字段表达式:
$query->like('username|nickname#name')会把username|nickname作为字段表达式传给 ThinkORM,具体 SQL 以 ThinkORM 条件解析为准 - IN 查询:
$query->in('status')- 读取status=1,2并生成whereIn('status', ['1', '2']) - 普通范围:
$query->valueBetween('amount')- 默认读取类似amount=10 99的输入并生成区间查询 - 日期范围:
$query->dateBetween('create_at')- 默认读取类似2026-01-01 - 2026-01-31的日期范围,自动补齐一天的开始和结束时间 - 时间戳范围:
$query->timeBetween('enter_time')- 与dateBetween()类似,但会把日期字符串转换为时间戳 - 两字段范围:
$query->valueRange('range_start:range_after#code')- 读取code,生成range_start <= code且range_after >= code的查询条件
另外对于 url 输入的变量也可以快速输入到查询器条件中,如:
以下示例代码中的 SystemUser 为数据模型名称或表名的驼峰名称。
// 1.0. 使用模型创建查询器(推荐方式)
// SystemUser 是已继承基础模型 \think\admin\Model 的模型类,mQuery() 方法是自定义的查询构建器。
$query = SystemUser::mQuery();
// 1.1. 控制器中使用原生查询器(指定表名或模型名)
$query = $this->_query('SystemUser');
// 2.0. 使用 layTable 方法(推荐方式,支持动态高度、搜索绑定)
SystemUser::mQuery()->layTable(function () {
$this->title = '系统用户管理';
}, function (QueryHelper $query) {
$query->where(['is_deleted' => 0, 'status' => 1]);
$query->like('username,nickname')->equal('status');
$query->dateBetween('create_at')->order('id desc');
});
// 2.1. 使用传入的 URL 参数进行查询条件设置
// 这里使用了 `like` 和 `equal` 方法分别处理模糊查询和精确查询。
$query->like('username,sex')->equal('status');
// 3.0. 执行分页查询,分页结果会自动返回,便于前端展示。
$query->page();
// 4.0. 使用别名来处理 URL 参数与数据库字段不匹配的情况。
// 例如,URL 参数 'username_alias' 对应数据库中的 'username' 字段,
// 'user_status' 对应数据库中的 'status' 字段。
$get = ['username_alias' => 'admin', 'user_status' => 1];
$query = $this->_query('SystemUser', $get)->like('username#username_alias,sex')->equal('status#user_status');
// 5.0. 执行分页查询。
$query->page();
// 6.0. 额外的列表数据赋值到模板:
// 这里查询所有状态为 1 的用户列表,并将结果传递给模板变量 $userList。
$this->userList = $this->_query('SystemUser')->where(['status' => 1])->select();
// 7.0. 接收 URL 参数,进行查询条件设置。
// 假设 URL 参数 'username' 和 'sex' 使用 `like` 模糊查询,'status' 使用 `equal` 精确查询。
$get = ['status' => 1, 'username' => 'admin'];
$query = $this->_query('SystemUser', $get)->like('username,sex')->equal('status');
// 8.0. 执行分页查询并返回结果。
$query->page();ThinkORM 关联查询(with)
with() 是 ThinkORM 的查询方法,QueryHelper 通过内部 Query 对象代理调用该方法:
// 1. 基础关联查询
SystemUser::mQuery()->layTable(function () {
$this->title = '系统用户管理';
}, function (QueryHelper $query) {
// 关联用户身份资料
$query->with(['userinfo' => static function ($query) {
$query->field('code,name,content');
}]);
// 设置查询条件
$query->where(['is_deleted' => 0, 'status' => 1]);
$query->like('username,nickname')->equal('status');
});
// 2. 多个关联查询
$query->with([
'userinfo' => static function ($query) {
$query->field('code,name,content');
},
'auth' => static function ($query) {
$query->field('id,title,status');
}
]);
// 3. 关联查询条件过滤
$query->with(['userinfo' => static function ($query) {
$query->where('status', 1)->field('code,name');
}]);
// 4. 关联查询排序
$query->with(['userinfo' => static function ($query) {
$query->order('id desc')->field('code,name');
}]);关联查询说明:
with()方法来自 ThinkORM 的关联预加载机制- 关联查询在模型层定义,需要在模型中定义关联关系
- 支持闭包函数自定义关联查询条件
- 可以同时加载多个关联关系,具体效果以 ThinkORM 行为为准
原生 SQL 子查询
使用原生SQL子查询做为了数据表,Sql 语句需要带别名,自 2025/02/16 之后才开始支持。
// 1. 模拟通过 GET 请求传递的查询条件,这里可以根据实际情况动态生成或获取条件
$get = ['username' => 'admin'];
// 2. 构建查询规则,创建 Query 查询对象。请注意:SQL 语句中必须包含别名以避免歧义。
$query = $this->_query('(SELECT * FROM system_user) AS a', $get);
// 3. 设置查询字段及条件。在使用别名时,条件也必须使用相应的别名。使用 like() 等 QueryHelper 方法进行模糊查询,确保条件正确应用。
$query->like('a.username#username'); // 假设查询用户名为 'admin'
// 4. 额外的过滤条件(可选)。可以根据需求继续链式添加查询条件。
$query->where('a.status', 1); // 假设 1 表示启用状态的用户
// 5. 使用 page() 分页输出结果,也可以直接调用 layTable() 或 select() 方法。
$result = $query->page(true, false);使用 JOIN 查询
使用 ThinkORM 的 join 操作,一定要设置好 field 以及查询条件必需指定别名。
// 1. 模拟通过 GET 请求传递的查询条件,这里可以根据实际情况动态生成或获取条件
$get = ['username' => 'admin'];
// 2. 构建查询规则,创建 Query 查询对象。设置 join 相关的参数与 ThinkORM 操作一样。
$query = $this->_query('system_user', $get)->alias('a')->join('system_oplog b', 'a.username=b.username');
// 3. 设置查询字段及条件。在使用别名时,条件也必须使用相应的别名。
// 使用 join 时,输出的结果字段可能会出现重复,这里需要指定 field 输出的字段
$query->like('a.username#username')->field('a.username,b.action');
// 4. 使用 page() 分页输出结果,也可以直接调用 layTable() 或 select() 方法。
$result = $query->page(true, false);
dump($result);查询回调处理
对于通过 page 或 layTable 输出的列表,还可以通过 _page_filter 对列表进行引用二次处理。PageHelper::init() 会传入列表和完整结果,get.layui.table 会传入 data 列表和 Layui 结果结构,如:
protected function _page_filter(array &$data, array &$result){
// 这里可以对 $data 进行二次处理,也可以读取或调整 $result
}当一个控制器存在多个page操作时,可以指定回调前缀
protected function _index_page_filter(array &$data, array &$result){
// 精准回调对 $data 进行二次处理,注意是引用
}查询使用案例
<?php
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
/**
* 系统操作日志
* @class Oplog
* @package app\admin\controller
*/
class Oplog extends Controller
{
/**
* 系统操作日志
* @auth true
* @menu true
* @throws \think\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function index()
{
$this->title = '系统操作日志';
$query = $this->_query($this->table)->like('action,node,content,username,geoip');
$query->dateBetween('create_at')->order('id desc')->page();
}
/**
* 列表数据处理
* @param array $data
* @throws \Exception
*/
protected function _index_page_filter(array &$data)
{
$ip = new \Ip2Region();
foreach ($data as &$vo) {
$result = $ip->btreeSearch($vo['geoip']);
$vo['isp'] = isset($result['region']) ? $result['region'] : '';
$vo['isp'] = str_replace(['内网IP', '0', '|'], '', $vo['isp']);
}
}
}快捷查询助手同样保留原 ThinkPHP Query 的功能,也可以使用 JOIN 进行关联查询。
// 在这里对 Db 进行操作,如果 $db->where()->buildSql() 等等
$db = $this->_query('表名')->db();
// or 模型调用法
$db = SystemUser::mQuery()->db();
// 输出 Sql 语句
var_dump($db->buildSql());
// 对于联表操作,需要指定 field,join 如果用到了条件查询,一定要用上别名查询
$query = $this->_query('table_a')->alias('a')->field('a.id,a.name,b.pid,b.nickname');
$query->join('table_b b','a.id=b.pid')->like('a.name#a_name')->page();
// 另外,对于 TP 原有的用法也支持如 | 和 & 用法
$map = ['a&b' => 1]; // 生成的条件是: where a = 1 and b = 1
$map = ['a|b' => 1]; // 生成的条件是: where b = 1 or b = 1
// 以上两种情况配合别名使用
$this->_query('table_a')->alias('a')->equal('a|b#name')...
// or 模型调用法
SystemUser::mQuery()->alias('a')->equal('a|b#name')...
// 当前请求带上 name 值时,会生成 where a=值 or b=值
$this->_query('table_a')->alias('a')->equal('a&b#name')...
// or 模型调用法
SystemUser::mQuery()->alias('a')->equal('a&b#name')...
// 当前请求带上 name 值时,会生成 where a=值 and b=值高级查询技巧
1. 子查询(SubQuery)
使用 buildSql() 方法构建子查询,适用于复杂的数据过滤场景:
// 实际应用:清理重复文件
$map = ['issafe' => 0, 'uuid' => AdminService::getUserId()];
// 构建子查询:获取每个 type 和 xkey 组合的最大 id
$subQuery = SystemFile::mk()
->fieldRaw('MAX(id) AS id')
->where($map)
->group('type, xkey')
->buildSql();
// 使用子查询删除重复数据(保留每组最大 id 的记录)
SystemFile::mk()
->where($map)
->whereRaw("id NOT IN ({$subQuery})")
->delete();2. 聚合查询(Aggregate Functions)
通过 db() 方法获取 ThinkORM 的 Query 对象,执行聚合查询:
// 获取 QueryHelper 的底层 Query 对象
$db = SystemUser::mQuery()->where(['status' => 1])->db();
// 统计查询
$count = $db->count(); // 记录总数
$total = $db->sum('amount'); // 金额总和
$avg = $db->avg('score'); // 平均分
$max = $db->max('create_at'); // 最新创建时间
$min = $db->min('create_at'); // 最早创建时间
// 分组统计
$stats = $db->field('status, COUNT(*) as count')
->group('status')
->select();
// 多字段聚合
$result = $db->field('COUNT(*) as total, SUM(amount) as sum, AVG(score) as avg')
->find();3. 复杂条件查询
使用 ThinkORM 的复杂条件语法:
// 使用 whereRaw 进行复杂条件查询
$query = SystemUser::mQuery()
->whereRaw('status = 1 AND (username LIKE ? OR nickname LIKE ?)', ['%admin%', '%admin%']);
// 使用 whereOr 进行 OR 条件组合
$query = SystemUser::mQuery()
->where(function ($query) {
$query->where('status', 1)->where('is_deleted', 0);
})
->whereOr(function ($query) {
$query->where('type', 'vip')->where('expire_at', '>', date('Y-m-d'));
});
// 使用 whereIn 和 whereNotIn
$query = SystemUser::mQuery()
->whereIn('id', [1, 2, 3, 4, 5])
->whereNotIn('status', [0, 2]);
// 使用 whereBetween 进行范围查询
$query = SystemUser::mQuery()
->whereBetween('create_at', ['2024-01-01', '2024-12-31']);
// 使用 whereNull 和 whereNotNull
$query = SystemUser::mQuery()
->whereNull('deleted_at')
->whereNotNull('email');4. 字段表达式和计算字段
// 使用 fieldRaw 添加计算字段
$query = SystemUser::mQuery()
->field('id, username, nickname')
->fieldRaw('TIMESTAMPDIFF(DAY, create_at, NOW()) as days_ago')
->fieldRaw('CASE WHEN status = 1 THEN "启用" ELSE "禁用" END as status_text');
// 使用 field 进行字段别名
$query = SystemUser::mQuery()
->field('id, username as name, nickname as display_name');
// 排除字段
$query = SystemUser::mQuery()
->withoutField('password, salt');5. 排序和分页优化
// 多字段排序
$query = SystemUser::mQuery()
->order('status desc, create_at desc, id asc');
// 随机排序
$query = SystemUser::mQuery()
->orderRaw('RAND()');
// 限制查询数量(用于 TOP N 查询)
$query = SystemUser::mQuery()
->order('create_at desc')
->limit(10); // 只查询前 10 条
// 分页查询(获取指定页数据)
$query = SystemUser::mQuery()
->page(2, false, false, 20); // 第 2 页,每页 20 条,返回数据数组6. 调试和性能优化
// 输出生成的 SQL 语句(用于调试)
$db = SystemUser::mQuery()->where(['status' => 1])->db();
$sql = $db->buildSql();
echo $sql; // 输出:SELECT * FROM `system_user` WHERE `status` = 1
// 使用 explain 分析查询性能
$explain = $db->explain(true);
dump($explain);
// 启用查询缓存(如果配置了缓存)
$result = SystemUser::mQuery()
->where(['status' => 1])
->cache(3600) // 缓存 1 小时
->select();
// 使用 distinct 去重
$query = SystemUser::mQuery()
->distinct(true)
->field('status, type');7. 实际应用案例:复杂业务查询
<?php
declare(strict_types=1);
namespace app\admin\controller;
use think\admin\Controller;
use think\admin\helper\QueryHelper;
use think\admin\model\SystemFile;
use think\admin\service\AdminService;
/**
* 系统文件管理
* @class File
* @package app\admin\controller
*/
class File extends Controller
{
/**
* 系统文件管理
* @auth true
* @menu true
*/
public function index()
{
SystemFile::mQuery()->layTable(function () {
$this->title = '系统文件管理';
// 获取所有文件扩展名(去重)
$this->xexts = SystemFile::mk()->distinct()->column('xext');
}, static function (QueryHelper $query) {
// 复杂查询条件
$query->like('name,hash,xext')
->equal('type')
->dateBetween('create_at')
->where([
'issafe' => 0,
'status' => 2,
'uuid' => AdminService::getUserId()
]);
});
}
/**
* 清理重复文件(使用子查询)
* @auth true
*/
public function distinct()
{
$map = ['issafe' => 0, 'uuid' => AdminService::getUserId()];
// 构建子查询:获取每个 type 和 xkey 组合的最大 id
$subQuery = SystemFile::mk()
->fieldRaw('MAX(id) AS id')
->where($map)
->group('type, xkey')
->buildSql();
// 删除不在子查询结果中的记录(即重复文件)
SystemFile::mk()
->where($map)
->whereRaw("id NOT IN ({$subQuery})")
->delete();
$this->success('清理重复文件成功!');
}
/**
* 列表数据后处理
* @param array $data
*/
protected function _page_filter(array &$data)
{
$types = Storage::types();
foreach ($data as &$vo) {
// 添加存储类型名称
$vo['ctype'] = $types[$vo['type']] ?? $vo['type'];
// 格式化文件大小
$vo['size_text'] = format_bytes($vo['xsize'] ?? 0);
}
}
}性能优化建议:
- 索引优化:确保查询字段有适当的索引
- 字段选择:只查询需要的字段,避免
SELECT * - 关联查询:需要预加载关联时使用 ThinkORM
with()方法 - 分页限制:合理设置分页大小,避免一次查询过多数据
- 查询缓存:对不经常变化的数据使用查询缓存
- 子查询优化:复杂子查询考虑使用 JOIN 替代
