Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 339

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 392

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 394
Yii2 joinWith left join 附加 on 额外 and 条件 - Joyber 的博客

Yii2 joinWith left join 附加 on 额外 and 条件

joinWith 默认把 closure 里面的条件放到 WHERE,不是 ON。想要 LEFT JOIN xxx ON a=b AND c=d,必须用 onCondition,不能写 andFilterWhere / andWhere

关键点

  • joinWith('关联名', false):第二个参数 false不执行 eager loading 预加载,只做 JOIN SQL;true 会额外做一次 IN 查询。
  • onCondition 里面写的条件,会拼接在 ON 后面,生成 AND xxx=xxx
  • andWhere / andFilterWhere 会跑到 WHERE 子句,会把 left join 变成等价 inner join,这是高频踩坑。

模型关联定义(必须先定义 relation)

// Order 模型
public function getLog()
{
    /**
     * LEFT JOIN log ON log.order_id = order.id AND log.type = 1
     */
    return $this->hasMany(Log::class, ['order_id' => 'id'])
        ->onCondition(['log.type' => 1]); // 这里的条件直接拼 ON AND
}
如果条件需要动态变量,用数组格式防注入:
->onCondition([
    'log.type' => $typeVal,
    'log.status' => 2
]);

查询使用 joinWith

$query = Order::find()
    ->joinWith([
        'log' => function($q) {
            // ❌ 不要在这里写 andWhere,会进 WHERE
            // ✅ 如果需要再追加 ON 的 AND,继续 onCondition
            $q->onCondition(['log.is_del' => 0]);
        }
    ], false) // false:只JOIN,不贪婪加载
    ->all();

生成 SQL:

SELECT `order`.* FROM `order`
LEFT JOIN `log` ON `log`.`order_id` = `order`.`id` AND `log`.`type`=1 AND `log`.`is_del`=0

这里说一下 joinWith 第二个参数 $eagerLoading(bool)

方法签名:

public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN')

$eagerLoading = false:只执行 SQL JOIN,不做贪婪(eager)预加载。

$eagerLoading = true(默认)

Order::find()->joinWith('log', true);

会执行2条SQL

  1. 主查询:SELECT order.* LEFT JOIN log ON ... 拿到主表+关联表连接后的数据集
  2. 额外再跑一条 IN 查询:SELECT * FROM log WHERE order_id IN (1,2,3...),把关联模型数据查出来,给模型的 $order->log 属性赋值。
目的:做贪婪加载,循环遍历 $orders 的时候,访问 $order->log 不会触发数据库查询。
代价:多一次数据库查询。即使你根本不需要读取 $order->log,这条SQL依旧会执行。

$eagerLoading = false

Order::find()->joinWith('log', false);

只执行1条SQL:带LEFT JOIN的主查询。

  • SQL里会做 LEFT JOIN,你可以在select里取log表字段、可以where过滤log表字段;
  • 不会额外执行IN查询,不会填充模型关联属性
  • 此时你访问 $order->log,会触发延迟加载(lazy load),单独再查一次数据库。

使用场景什么时候写 false

  1. 仅仅为了做表连接,用于过滤、排序、取关联表字段,业务代码不读取 $model->relation

    绝大多数报表、列表查询场景,都用 false,节省一次SQL。
    // 用于 where、orderBy、select取log.*,不需要读取$order->log
    Order::find()
     ->joinWith('log',false)
     ->select('order.*,log.content')
     ->andWhere(['log.type'=>1])
     ->orderBy('log.create_time desc');
  2. 你只是需要 LEFT JOIN xxx ON ... AND ...,不需要拿到关联模型对象。

什么时候用 true(默认)

需要循环遍历结果,并且要读取关联模型属性:

$list = Order::find()->joinWith('log',true)->all();
foreach($list as $item){
    echo $item->log[0]->content; // 不会N+1,全部已经预加载完毕
}

场景2:不想修改模型关联,查询时临时追加 ON AND

模型本身不带 onCondition,查询动态增加 ON 条件

$query = Order::find()
    ->joinWith([
        'log' => function ($q) {
            /** @var $q ActiveQuery */
            // 追加 ON AND 条件,不会污染模型定义
            $q->onCondition([
                'log.type' => 1,
                'log.is_del' => 0
            ]);
        }
    ], false)
    ->all();

场景3:onCondition 需要大于、不等于这类非等于条件

onCondition 支持 yii 的条件数组格式:

$q->onCondition([
    'and',
    ['log.type' => 1],
    ['>', 'log.create_time', '2026-01-01 00:00:00']
]);

输出:ON ... AND log.type=1 AND log.create_time > '2026‑01‑01 00:00:00'

场景4:原生 SQL 习惯,直接手写 join(备选)

如果逻辑复杂,也可以直接 leftJoin,完全手写on:

Order::find()
    ->leftJoin('log', 'log.order_id = `order`.id AND log.type = :t', [':t' => 1])
    ->all();

常见错误对比

❌ 错误写法(条件跑到 WHERE,left join 失效)

->joinWith(['log' => function($q){
    $q->andWhere(['log.type' =>1]); // 条件进 WHERE,不是 ON
}], false);

✅ 正确:onCondition,条件拼接在 ON ... AND xxx

小提示

  1. joinWith(relName, false):只做表连接,不查询关联数据;如果需要同时取出关联模型数据,第二个参数改为 true
  2. onCondition 是 ActiveQuery 的方法,只作用于 JOIN 的 ON,不会影响 where。
  3. 如果使用 andFilterWhere,同样会跑到 WHERE,绝对不要拿来写 ON 的 and 条件

标签: none

添加新评论