J'ai 3 modèles
relations de modèle
user
ont belongsToMany('App\Channel');
channel
ont hasMany('App\Reply', 'channel_id', 'id')->oldest();
disons que j'ai 2 canaux - canal 1 - canal 2
channel-2
a des réponses plus récentes que channel-1
maintenant, je veux commander la chaîne de l'utilisateur par la réponse actuelle de sa chaîne. tout comme une application de chat. comment puis-je commander la chaîne de l'utilisateur comme ça?
j'ai déjà essayé quelques codes. mais rien ne se passe
// User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies'])
->orderBy('replies.created_at'); // error
}
// also
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies' => function($qry) {
$qry->latest();
}]);
}
// but i did not get the expected result
[~ # ~] modifier [~ # ~] aussi, j'ai essayé cela. oui j'ai obtenu le résultat attendu mais il ne chargerait pas tous les canaux s'il n'y a pas de réponse.
public function channels()
{
return $this->belongsToMany('App\Channel')
->withPivot('is_approved')
->join('replies', 'replies.channel_id', '=', 'channels.id')
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'ASC');
}
ÉDITER:
Selon mes connaissances, charge ardente with
méthode exécute la 2ème requête. C'est pourquoi vous ne pouvez pas obtenir ce que vous voulez avec la méthode de chargement rapide with
.
Je pense que l'utilisation de la méthode join
en combinaison avec la méthode de relation est la solution. La solution suivante est entièrement testée et fonctionne bien.
// In User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved');
}
public function sortedChannels($orderBy)
{
return $this->channels()
->join('replies', 'replies.channel_id', '=', 'channel.id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Ensuite, vous pouvez appeler $user->sortedChannels('desc')
pour obtenir la liste des canaux classés par réponses created_at
Attribut.
Pour des conditions comme les canaux (qui peuvent ou non avoir des réponses), utilisez simplement la méthode leftJoin
.
public function sortedChannels($orderBy)
{
return $this->channels()
->leftJoin('replies', 'channel.id', '=', 'replies.channel_id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Modifier:
Si vous souhaitez ajouter la méthode groupBy
à la requête, vous devez porter une attention particulière à votre clause orderBy
. Parce que dans la nature SQL , la clause Group By
Est exécutée en premier avant la clause Order By
. Voir le détail de ce problème sur cette question stackoverflow .
Donc, si vous ajoutez la méthode groupBy
, vous devez utiliser la méthode orderByRaw
et devez être implémentée comme suit.
return $this->channels()
->leftJoin('replies', 'channels.id', '=', 'replies.channel_id')
->groupBy(['channels.id'])
->orderByRaw('max(replies.created_at) desc')
->get();
Dans votre classe de canal, vous devez créer cette relation hasOne
(vous canalisez hasMany
réponses, mais elle hasOne
dernière réponse =):
public function latestReply()
{
return $this->hasOne(\App\Reply)->latest();
}
Vous pouvez maintenant obtenir tous les canaux classés par dernière réponse comme ceci:
Channel::with('latestReply')->get()->sortByDesc('latestReply.created_at');
Pour obtenir toutes les chaînes de l'utilisateur commandées par dernière réponse, vous aurez besoin de cette méthode:
public function getChannelsOrderdByLatestReply()
{
return $this->channels()->with('latestReply')->get()->sortByDesc('latestReply.created_at');
}
où channels()
est donné par:
public function channels()
{
return $this->belongsToMany('App\Channel');
}
Tout d'abord, vous n'avez pas besoin de spécifier le nom du tableau croisé dynamique si vous suivez convention de dénomination de Laravel pour que votre code soit un peu plus propre:
public function channels()
{
return $this->belongsToMany('App\Channel') ...
Deuxièmement, vous devez appeler join
explicitement pour obtenir le résultat en une seule requête:
public function channels()
{
return $this->belongsToMany(Channel::class) // a bit more clean
->withPivot('is_approved')
->leftJoin('replies', 'replies.channel_id', '=', 'channels.id') // channels.id
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'desc');
}
Si vous avez une relation hasOne()
, vous pouvez trier tous les enregistrements en faisant:
$results = Channel::with('reply')
->join('replies', 'channels.replay_id', '=', 'replies.id')
->orderBy('replies.created_at', 'desc')
->paginate(10);
Cela trie tous les enregistrements channels
par les réponses les plus récentes (en supposant que vous n'avez qu'une seule réponse par canal.) Ce n'est pas votre cas, mais quelqu'un peut chercher quelque chose comme ça (comme je l'étais.)