web-dev-qa-db-fra.com

ActiveRecord où et ordre sur via-table

J'ai trois tables de base de données:

produit (identifiant, nom)

product_has_adv (produit, avantage, tri, important)

avantage (id, texte)

Dans ProductModel, j'ai défini ceci:

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

Je profite des avantages sans aucun problème.

Mais maintenant, je dois ajouter un produit où product_has_advantage.important = 1 clausel et trier les avantages en fonction du tri-colonne dans la table product_has_advantage.

Comment et où dois-je le réaliser?

16
rakete

L'utilisation de méthodes via et viaTable avec des relations provoquera deux requêtes distinctes.

Vous pouvez spécifier callable en troisième paramètre comme ceci:

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

Le filtre par important sera appliqué, mais le tri ne le sera pas puisqu'il se produit lors de la première requête. En conséquence, l'ordre des identifiants dans l'instruction IN sera modifié.

En fonction de la logique de votre base de données, il est peut-être préférable de déplacer les colonnes important et sort vers la table advantage.

Ensuite, ajoutez simplement condition et triez dans la chaîne de méthodes existante:

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

L'utilisation de méthodes viaTable avec des relations génère deux requêtes distinctes, mais si vous n'avez pas besoin de la méthode link(), vous pouvez utiliser innerJoin de la manière suivante pour trier par table 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;
}

Notez que $query->multiple = true vous permet d'utiliser cette méthode comme relation Yii2 hasMany.

3
Martín M

Juste pour référence https://github.com/yiisoft/yii2/issues/10174 Il est presque impossible de ORDER BY viaTable() colonnes . Pour Yii 2.0.7, il retourne un ensemble d’ID de la requête viaTable(), La clause __. and final/top query IN() ignore la commande.

3
shikotanorama

Tout d'abord, vous devez créer un modèle nommé ProductHasAdv pour la table de jonction (product_has_adv) à l'aide de CRUD.

Créez ensuite la relation dans le modèle product et triez-la:

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

Ensuite, créez une deuxième relation comme ceci:

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

Cela triera le résultat final en utilisant la technique order by FIELD.

N'oubliez pas d'ajouter:

use yii\db\Expression;

ligne à la tête.

1
Tural Ali

Pour ceux qui viennent ici après un certain temps et qui n'aiment pas les solutions ci-dessus, je l'ai fait fonctionner en rejoignant la table via après le filtre via table.

Exemple pour le code ci-dessus:

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

Prenez soin de changer XXX avec le bon chemin de jointure et YYY avec la bonne colonne de tri.

1
MIke

Je me suis débrouillé pour savoir comment ... mais cela demande plus de travail après ... .. Le problème est que vous devez d'abord interroger la relation plusieurs-à-plusieurs à partir du modèle source, puis dans cette fermeture, vous devez interroger votre modèle cible .

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

Ensuite, il vous suffit de personnaliser le résultat trié selon vos besoins ..___ Le résultat de chaque ligne ressemblerait à

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