闭包方式支持in/exists子查询,buildsql生成派生表sql,子查询可前置过滤分组数据,fetchsql用于调试真实sql。

一、使用闭包方式构造IN子查询
在报表统计中,当需要筛选出符合某子集条件的主表记录时,IN子查询是最直观的方式。闭包写法由框架自动处理参数绑定与括号包裹,避免SQL注入且兼容性高。
1、在主查询的where方法中,将第三个参数设为匿名函数,传入$query参数。
2、在闭包内调用$table()显式指定子查询表名,并通过field()限定返回字段。
3、子查询中不得调用select()或find()等执行方法,仅构建查询结构。
4、例如统计“有已完成订单的用户”:$users = Db::name('user')->where('id', 'in', function($query) { $query->table('order')->where('status', 'completed')->field('user_id'); })->select();
二、使用闭包方式构造EXISTS子查询
当主表与子表存在逻辑关联但无需返回子表字段时,EXISTS子查询性能通常优于IN,尤其在子表数据量大而匹配率低的场景下。该方式依赖关联条件表达式,不提取具体值。
1、调用whereExists方法,传入一个接收$query参数的闭包。
2、在闭包中使用$table()指定子表,并通过whereRaw编写关联条件,确保字段歧义已消除。
3、必须手动添加表别名并在条件中引用,如$query->table('order o')->whereRaw('o.user_id = user.id')。
4、例如筛选“存在近30天支付记录的用户”:$users = Db::name('user')->whereExists(function($query) { $query->table('payment p')->whereRaw('p.user_id = user.id AND p.created_at >= ?', [date('Y-m-d H:i:s', strtotime('-30 days'))]); })->select();
三、使用buildSql生成子查询SQL并配合whereRaw拼接
当需将子查询作为FROM子句的一部分(如派生表),或需对子查询结果进一步JOIN、GROUP BY时,buildSql可生成带括号的完整SELECT语句字符串,供上层查询直接引用。
1、先构建子查询对象,调用field()明确所需字段,避免SELECT *导致语法错误。
2、在子查询对象上调用buildSql(),返回形如"(SELECT user_id FROM order WHERE status = ?)"的字符串。
3、将该字符串代入whereRaw或table()方法中,注意手动补全空格与括号层级。
4、例如按用户聚合最近订单数:$subSql = Db::table('order')->where('created_at', '>=', '2026-04-22')->field('user_id, COUNT(*) as cnt')->group('user_id')->buildSql(); $result = Db::table($subSql . ' t')->join('user u', 't.user_id = u.id')->field('u.name, t.cnt')->select();
四、将子查询用于分组统计的前置过滤
在复杂报表中,若需先限定参与分组的数据范围(如仅统计审核通过的订单),直接在主查询加WHERE可能导致索引失效或扫描行数激增。此时可将过滤逻辑下沉至子查询,再对子结果集进行GROUP BY。
1、构造子查询获取符合条件的基础记录集,仅保留关键关联字段和度量字段。
2、调用buildSql()获取子查询SQL,并将其作为新表名传入Db::table()。
3、在新表上下文中执行field()、group()、sum()等聚合操作,确保分组字段全部显式声明。
4、例如统计各地区“已签收”订单总金额:$subQuery = Db::table('order')->where('status', 'signed')->field('region_id, amount'); $sql = $subQuery->buildSql(); $result = Db::table($sql . ' o')->field('o.region_id, SUM(o.amount) AS total')->group('o.region_id')->select();
五、结合fetchSql调试子查询真实语句
子查询逻辑错误常表现为字段不存在、括号缺失或参数绑定异常。fetchSql(true)可跳过执行直接返回完整SQL,便于粘贴至数据库客户端验证语法与执行计划。
1、在子查询对象上调用fetchSql(true),返回不含占位符的可执行SQL字符串。
2、将该SQL复制到MySQL命令行,前置加上EXPLAIN关键字分析type、key、rows等关键指标。
3、重点确认子查询是否命中索引、是否存在隐式类型转换、GROUP BY字段是否完整。
4、例如调试用户活跃度子查询:$sub = Db::table('login_log')->where('login_time', '>=', '2026-05-01')->field('user_id')->group('user_id'); echo $sub->fetchSql(true); 输出结果应为 SELECT `user_id` FROM `login_log` WHERE `login_time` >= '2026-05-01' GROUP BY `user_id`
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











