在 Yii2 中,Model::find()->with('user')->... 是关联查询的常用语法,但其底层实现和你提到的 model()->with(user)->name 存在一些细节需要澄清:
1. 正确的关联查询语法
首先,model()->with(user)->name 不是正确的语法,正确的写法是通过模型的 find() 方法发起查询,例如:
// 正确:查询主表数据并关联查询 user 表
$orders = Order::find()
->with('user') // 关联名为 user 的关系
->where(['status' => 1])
->all();
// 访问关联数据
foreach ($orders as $order) {
echo $order->user->name; // 获取关联的用户名称
}
with('user') 中的 user 是模型中定义的关联名称(需在 Order 模型中通过 getUser() 方法定义关联关系)。
2. 底层实现:两种关联查询方式
Yii2 的关联查询有两种实现方式,默认使用延迟加载,with() 方法则会触发即时加载(减少 SQL 次数):
(1)即时加载(with() 方法):1 次主表查询 + 1 次关联表 IN 查询
当使用 with('user') 时,Yii2 会执行两次 SQL:
- 先查询主表(如
order 表),获取符合条件的所有记录(例如 SELECT * FROM order WHERE status = 1); - 提取主表记录中关联字段的值(如
user_id),用 IN 条件批量查询关联表(如 SELECT * FROM user WHERE id IN (1,2,3,...),其中 1,2,3 是主表的 user_id 集合)。
优点:无论主表有多少条记录,仅执行 2 次 SQL,性能更优(避免 N+1 问题)。
适用场景:已知需要访问关联数据时,优先使用 with()。
(2)延迟加载(不使用 with()):1 次主表查询 + N 次关联表查询
如果不使用 with('user'),直接在访问关联数据时才查询:
$orders = Order::find()->where(['status' => 1])->all();
foreach ($orders as $order) {
echo $order->user->name; // 首次访问 user 时才查询关联表
}
Yii2 会执行:
- 1 次主表查询(
order 表); - 对每条主表记录,执行 1 次关联表查询(
user 表),即 N 条主表记录对应 N 次查询(N+1 问题)。
缺点:数据量大时性能极差(例如 100 条主表记录会执行 101 次 SQL)。
适用场景:仅需访问极少数主表记录的关联数据时(如单条记录)。
3. 与 JOIN 查询的区别
with('user') 本质是两次独立的查询(主表 + 关联表 IN 查询),然后由 Yii2 在内存中拼接关联数据,并非 SQL 层面的 JOIN 操作。
如果需要用 JOIN 实现关联查询(单条 SQL),需使用 joinWith() 方法:
// JOIN 查询:主表与关联表通过 SQL JOIN 合并查询
$orders = Order::find()
->joinWith('user') // 等价于 INNER JOIN user ON order.user_id = user.id
->where(['order.status' => 1])
->andWhere(['user.status' => 1]) // 可直接过滤关联表字段
->all();
joinWith() 会生成包含 JOIN 子句的 SQL,适合需要通过关联表字段筛选主表数据的场景。
总结
Model::find()->with('user') 是正确的关联查询语法,用于即时加载关联数据,底层执行 2 次 SQL(主表 + 关联表 IN 查询),而非 JOIN;- 不使用
with() 时,会触发延迟加载,导致 N+1 次 SQL 查询,性能较差; - 若需 SQL 层面的 JOIN 关联,使用
joinWith() 方法。
根据业务场景选择合适的关联查询方式,优先用 with() 避免 N+1 问题,需关联筛选时用 joinWith()。
想一次性join查询并返回的数据中带出关联表字段怎么写?
在 Yii2 中,若要通过 JOIN 查询一次性关联表并返回关联表字段,需使用 joinWith() 方法(而非 with()),它会在 SQL 层面执行 JOIN 操作,并允许直接获取关联表的字段。
步骤说明
- 确保模型已定义关联关系(如主表
Order 关联 User); - 使用
joinWith() 执行 JOIN 查询(支持 INNER JOIN、LEFT JOIN 等); - 通过
select() 指定需要查询的主表和关联表字段; - 直接访问关联表字段(可通过模型属性或数组形式获取)。
示例代码
1. 模型定义关联关系
假设 Order 模型(主表 order)关联 User 模型(关联表 user),先在 Order 中定义关联:
// models/Order.php
namespace app\models;
use yii\db\ActiveRecord;
class Order extends ActiveRecord
{
// 定义与 User 的关联(假设 order.user_id = user.id)
public function getUser()
{
return $this->hasOne(User::class, ['id' => 'user_id']);
}
}
2. 执行 JOIN 查询并获取关联表字段
use app\models\Order;
// 1. 执行 JOIN 查询,指定要查询的主表和关联表字段
$orders = Order::find()
// JOIN 关联(默认 INNER JOIN,可指定为 LEFT JOIN)
->joinWith('user') // 等价于 INNER JOIN `user` ON `order`.`user_id` = `user`.`id`
// 或指定 LEFT JOIN:->joinWith('user', false, 'LEFT JOIN')
// 2. 选择需要的字段(主表字段 + 关联表字段,需加表名/别名前缀)
->select([
'order.id AS order_id', // 主表字段(加表名前缀避免冲突)
'order.order_no', // 主表订单号
'user.id AS user_id', // 关联表用户ID
'user.username', // 关联表用户名
'user.phone' // 关联表用户电话
])
// 3. 可选:添加查询条件(可过滤主表或关联表字段)
->where(['order.status' => 1]) // 主表条件
->andWhere(['user.status' => 1]) // 关联表条件
->asArray() // 可选:返回数组形式(便于直接获取字段)
->all();
// 打印结果
print_r($orders);
3. 结果示例
[
[
'order_id' => 1001,
'order_no' => '20231001001',
'user_id' => 123,
'username' => '张三',
'phone' => '13800138000'
],
// ... 更多记录
]
关键说明
joinWith() 与 with() 的区别:
with():执行两次查询(主表 + 关联表 IN 查询),内存中拼接数据,不支持直接通过主模型获取关联表字段(需通过 $model->user->field 访问);joinWith():执行一次 JOIN 查询,关联表字段与主表字段在同一结果集中,可直接获取。
字段前缀:
- 若主表和关联表有同名字段(如
id),必须用表名/别名前缀区分(如 order.id AS order_id、user.id AS user_id),否则会出现字段覆盖。
asArray() 的作用:
- 加上
asArray() 后,查询结果以数组形式返回,可直接通过 $item['username'] 获取关联表字段; - 若不加,返回的是模型对象,需通过
$model->username 或 $model->user->username 访问(两种方式均可,因 JOIN 已将字段合并)。
JOIN 类型:
- 默认是
INNER JOIN(仅返回关联表有对应记录的数据); 若需返回主表所有记录(即使关联表无数据),需指定为 LEFT JOIN:
->joinWith('user', false, 'LEFT JOIN')
总结
通过 joinWith() 配合 select() 可实现一次性 JOIN 查询并返回关联表字段,核心是:
- 用
joinWith() 执行 SQL 层面的 JOIN; - 用表名前缀指定关联表字段,避免冲突;
- 按需使用
asArray() 控制返回格式。
这种方式适合需要通过关联表字段筛选数据,或希望一次性获取所有字段的场景。