ActiveRecord where and order on via-table

前端 未结 8 700
南旧
南旧 2020-12-16 12:22

I have three database table:

product (id, name)

product_has_adv (product,advantage,sort,important)

advantage (id, text)

In ProductModel I def

相关标签:
8条回答
  • 2020-12-16 12:58

    First you need to create a model named ProductHasAdv for junction table (product_has_adv) using CRUD.

    Then create relation in product model and sort it:

      public function getAdvRels()
        {
            return $this->hasMany(ProductHasAdv::className(), ['product' => 'id'])->
            orderBy(['sort' => SORT_ASC]);;
        }
    

    Then create second relationship like this:

    public function getAdvantages()
    {
        $adv_ids = [];
        foreach ($this->advRels as $adv_rel)
            $adv_ids[] = $adv_rel->advantage;
        return $this->hasMany(Advantage::className(), ['id' => 'advantage'])->viaTable('product_has_adv', ['product' => 'id'])->orderBy([new Expression('FIELD (id, ' . implode(',', $adv_ids) . ')')]);
    }
    

    This will sort final result using order by FIELD technique.

    Don't forget to add:

    use yii\db\Expression;
    

    line to head.

    0 讨论(0)
  • 2020-12-16 12:58
    public function getAdvantages()
    {
        return $this
            ->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
            ->viaTable('product_has_advantage', ['product' => 'id'])
            ->andWhere(['important' => 1])
            ->orderBy(['sort' => SORT_DESC]);
    }
    
    0 讨论(0)
  • 2020-12-16 13:05

    I`ve managed this some how... but it needs additional work after. The point is that you have to query many-to-many relation first from source model and after that inside that closure you should query your target model.

            $query = Product::find();
            $query->joinWith([
                             'product_has_adv' => function ($query)
                             {
                                $query->alias('pha');
                                $query->orderBy('pha.sort ASC');
                                $query->joinWith(['advantage ' => function ($query){
                                    $query->select([
                                                'a.id',
                                                'a.text',
                                                ]);
                                     $query->alias('a');
                                }]);
                             },
                             ]);
    

    Then you just have to prettify the sorted result to your needs. The result for each row would look like

            "product_has_adv": [
            {
                "product": "875",
                "advantage": "true",
                "sort": "0",
                "important": "1",
                "advantage ": {
                    "id": "875",
                    "text": "Some text..",
                }
            },
    
    0 讨论(0)
  • 2020-12-16 13:06

    Just for reference https://github.com/yiisoft/yii2/issues/10174 It's near impossible to ORDER BY viaTable() columns. For Yii 2.0.7 it returns set of ID's from viaTable() query, and final/top query IN() clause ignores the order.

    0 讨论(0)
  • 2020-12-16 13:06

    For who comes here after a while and don't like above solutions, I got it working by joining back to the via table after the filter via table.

    Example for above code:

    public function getAdvantages()
    {
        return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
            ->viaTable('product_has_advantage', ['product' => 'id'])
            ->innerJoin('product_has_advantage','XXX')
            ->orderBy('product_has_advantage.YYY'=> SORT_ASC);
    }
    

    Take care about changing XXX with the right join path and YYY with the right sort column.

    0 讨论(0)
  • 2020-12-16 13:15

    Using via and viaTable methods with relations will cause two separate queries.

    You can specify callable in third parameter like this:

    public function getAdvantages()
    {
        return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
            ->viaTable('product_has_advantage', ['product' => 'id'], function ($query) {
                /* @var $query \yii\db\ActiveQuery */
    
                $query->andWhere(['important' => 1])
                    ->orderBy(['sort' => SORT_DESC]);
            });
    }
    

    The filter by important will be applied, but the sort won't since it happens in first query. As a result the order of ids in IN statement will be changed.

    Depending on your database logic maybe it's better to move important and sort columns to advantage table.

    Then just add condition and sort to the existing method chain:

    public function getAdvantages()
    {
        return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
            ->viaTable('product_has_advantage', ['product' => 'id'])
            ->andWhere(['important' => 1])
            ->orderBy(['sort' => SORT_DESC]);
    }
    
    0 讨论(0)
提交回复
热议问题