web-dev-qa-db-ja.com

ActiveRecordどこで、経由テーブルで注文する

3つのデータベーステーブルがあります。

製品(ID、名前)

product_has_adv(製品、利点、ソート、重要)

利点(ID、テキスト)

ProductModelでこれを定義しました:

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

問題なく利点を得ることができます。

しかし、ここで、where product_has_advantage.important = 1句を追加し、product_has_advantage-tableのsort-columenで利点をソートする必要があります。

どのように、どこでそれを実現する必要がありますか?

17
rakete

リレーションでviaメソッドとviaTableメソッドを使用すると、2つの別個のクエリが発生します。

次のように、3番目のパラメーターでcallableを指定できます。

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]);
        });
}

importantによるフィルターが適用されますが、最初のクエリで発生するため、並べ替えは行われません。その結果、INステートメントのidの順序が変更されます。

データベースロジックに応じて、important列とsort列をadvantageテーブルに移動する方がよい場合があります。

次に、条件を追加して、既存のメソッドチェーンに並べ替えます。

public function getAdvantages()
{
    return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
        ->viaTable('product_has_advantage', ['product' => 'id'])
        ->andWhere(['important' => 1])
        ->orderBy(['sort' => SORT_DESC]);
}
34
arogachev

リレーションでviaTableメソッドを使用すると、2つの別個のクエリが発生しますが、link()メソッドが必要ない場合は、以下の方法でinnerJoinを使用してproduct_has_advantageテーブルでソートできます。

public function getAdvantages()
{
    $query = AdvantageModel::find();
    $query->multiple = true;
    $query->innerJoin('product_has_advantage','product_has_advantage.advantage = advantage.id');
    $query->andWhere(['product_has_advantage.product' => $this->id, 'product_has_advantage.important' => 1]);
    $query->orderBy(['product_has_advantage.sort' => SORT_DESC]);
    return $query;
}

$query->multiple = trueでは、Yii2 hasMany関係としてこのメ​​ソッドを使用できることに注意してください。

5
Martín M

参考のために https://github.com/yiisoft/yii2/issues/10174ORDER BY viaTable()カラムはほとんど不可能です。 Yii 2.0.7の場合、viaTable()クエリからIDのセットを返し、final/top query IN()句は順序を無視します。

3
shikotanorama

最初に、CRUDを使用して、ジャンクションテーブル(product_has_adv)のProductHasAdvという名前のモデルを作成する必要があります。

次に、productモデルでリレーションを作成し、並べ替えます。

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

次に、次のような2番目の関係を作成します。

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) . ')')]);
}

これは、order by FIELDテクニックを使用して最終結果をソートします。

追加することを忘れないでください:

use yii\db\Expression;

行頭。

1
Tural Ali

しばらくしてここに来て、上記の解決策が気に入らない人のために、フィルターviaテーブルの後にviaテーブルに戻って参加しました。

上記のコードの例:

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);
}

正しい結合パスでXXXを変更し、正しいソート列でYYYを変更することに注意してください。

1
MIke

私はこれをどうにかして管理しました...しかし、後に追加の作業が必要です。ポイントは、最初にソースモデルから多対多リレーションをクエリする必要があることです。その後、そのクロージャ内でターゲットモデルをクエリする必要があります。

        $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');
                            }]);
                         },
                         ]);

次に、ソートされた結果を必要に応じて整形するだけです。各行の結果は次のようになります

        "product_has_adv": [
        {
            "product": "875",
            "advantage": "true",
            "sort": "0",
            "important": "1",
            "advantage ": {
                "id": "875",
                "text": "Some text..",
            }
        },
0
Stas Panyukov

@arogachevで説明したように、viaTableは2つの別個のクエリを使用し、中間のorderByを廃止します

@MartinMと同様のソリューションで、次のようにviaTableinnerJoinに置き換えることができます。

_public function getAdvantages()
{
   return $this->hasMany(AdvantageModel::class, ['pha.product' => 'id'])
       ->innerJoin('product_has_advantage pha', 'pha.advantage = advantage.id')
       ->andWhere(['pha.important' => 1])
       ->orderBy(['pha.sort' => SORT_ASC]);
}
_

hasManyの結果を調整することにより、ターゲットクラスのクエリを調整しています-AdvantageModel::find(); _product_has_advantage_は、advantage IDを介して結合できます

hasManyの2番目のパラメーターlinkは、_[ query.column => $this->attribute ]_として表示できます。これは、結合された_product_has_advantage_とそのproductアイデンティティーを介してサポートできるようになりました。


viaTableを使用する場合、リンクパラメータは中間クエリが完了し、そこから開始しているように表示できます。 _[ query.column => viaTable.column ]_したがって、あなたの質問で_['id', 'advantage']_

0
Arth