thinkphp执行复杂sql需区分查询与修改操作,严格使用db::query()(仅select/show/explain)和db::execute()(仅insert/update/delete),子查询须分清where级(用闭包)与from级(tp6用buildsql(),tp5需手写),标识符须白名单校验,动态字段用exp表达式。

ThinkPHP 执行复杂 SQL 不能靠拼字符串硬写,也不能指望链式方法自动覆盖所有场景;必须分清「查」还是「改」、分清「条件级子查询」还是「FROM 级派生表」,再选对方法和调用顺序。
Db::query() 和 Db::execute() 必须严格按 SQL 类型分开用
混用会导致静默失败:查数据返回空数组 [],后续 foreach 直接崩;改数据返回数字(如 1),却被当数据遍历。
-
Db::query()只能用于SELECT/SHOW/EXPLAIN,返回二维数组,适合模板volist或 PHP 遍历 -
Db::execute()专用于INSERT/UPDATE/DELETE/REPLACE,只返回影响行数(int)或false - TP5 不支持问号占位符在 SQL 字符串里自动绑定,必须显式传参数数组:
Db::query("SELECT * FROM user WHERE status = ? AND name LIKE ?", [1, "%{$kw}%"]) - 表名、字段名、
ORDER BY等标识符不能用占位符,必须白名单校验后拼进字符串,例如:in_array($sort, ['id', 'name']) ? "ORDER BY {$sort} DESC" : "ORDER BY id DESC"
子查询要分清楚是 WHERE 级还是 FROM 级
WHERE 级(如 IN、EXISTS)可用闭包,FROM 级(即派生表)必须用 buildSql(),两者语法和约束完全不同。
- WHERE 级子查询:直接在
where()第三个参数传闭包,闭包内只构造Query对象,严禁调用select()或find(),否则返回数组而非 Query,TP6 报InvalidArgumentException - EXISTS 子查询必须用
whereExists(),且闭包中要用whereRaw()显式写出关联条件,例如:$query->table('order o')->whereRaw('o.user_id = user.id AND o.status = ?', [1]) - FROM 级子查询(如先聚合再 JOIN):必须用
buildSql(),且子查询对象要显式指定字段(禁止SELECT *),否则生成的派生表无列名,JOIN 时字段引用失败 - 拼别名时注意格式:
table($subQuery->buildSql() . ' t')—— 括号已由框架加好,末尾不能有空格,别名前必须有空格
_fetchSql(true) 是调试唯一可靠入口,但输出含占位符
_fetchSql() 默认不返回字符串,必须传 true 参数;它输出的是带 ? 或 :bind_0 的预处理语句,不是可直接执行的完整 SQL。
- 正确用法:
$sql = $query->where('status', 1)->join('order o', 'o.user_id = user.id')->_fetchSql(true); - 想看真实参数值,得开调试模式(
'SHOW_SQL' => true),然后查Db::getLog()或日志文件 - 涉及
IN查询或UNION时,_fetchSql(true)输出可能被截断或结构异常,建议分段调用验证,比如加完join()就立刻试一次 - 手动写了
alias()或table()后,_fetchSql(true)输出里可能混用原表名和别名,容易误判字段归属,需对照实际 SQL 结构核对
动态字段/表达式要用 exp 查询表达式
ThinkPHP 不支持把字段名或函数名当参数绑定,遇到 CASE WHEN、IF、IS NOT NULL、ORDER BY FIELD() 这类 MySQL 原生语法,只能走 exp 表达式。
- 字段不为空:
where(['pic' => ['exp', 'IS NOT NULL']])或where('pic IS NOT NULL')(后者更直观,但会破坏数组条件统一性) - 条件求和:
field(["sum(if(type in (1,2),money,NULL)) as income", "sum(if(type=3,money,NULL)) as expend"]) - 按指定值排序:
orderRaw("FIELD(status, 1, 2, -1)"),注意orderRaw()是 TP5.1+ 才有,TP5.0 需用order('FIELD(status, 1, 2, -1)', '')配合空第二个参数绕过自动加反引号 -
exp表达式也支持写入:$data['score'] = ['exp', 'score + 1']; $model->save($data);
最易忽略的一点:TP6 的 buildSql() 返回值是带括号的完整子查询字符串,但 TP5 没这个方法,必须手写字符串并确保参数绑定安全——这意味着 TP5 复杂子查询要么降级为原生 SQL,要么升级到 TP6。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











