web-dev-qa-db-fra.com

Laravel Eloquent Join vs Inner Join?

J'ai donc du mal à comprendre comment faire un appel mysql de style d'alimentation, et je ne sais pas s'il s'agit d'un problème éloquent ou d'un problème mysql. Je suis sûr que c'est possible dans les deux cas et j'ai juste besoin d'aide.

J'ai donc un utilisateur et ils vont à leur page de fil, sur cette page il montre des choses de leurs amis (votes d'amis, commentaires d'amis, mises à jour de statut d'amis). Alors disons que j'ai Tom, Tim et Taylor comme mes amis et que je dois obtenir tous leurs votes, commentaires, mises à jour de statut. Comment puis-je m'y prendre? J'ai une liste de tous les amis par numéro d'identifiant, et j'ai des tableaux pour chacun des événements (votes, commentaires, mises à jour de statut) dans lesquels l'identifiant est stocké pour permettre la liaison avec l'utilisateur. Alors, comment puis-je obtenir toutes ces informations en même temps afin de pouvoir les afficher dans un flux sous la forme de.

Tim a commenté "Cool"

Taylor a déclaré "Première mise à jour du statut Woot ~!"

Taylor a été élu "Meilleur concours de tous les temps"

Edit @damiani Donc, après avoir modifié le modèle, j'ai un code comme celui-ci, qui renvoie les bonnes lignes

$friends_votes = $user->friends()->join('votes', 'votes.userId', '=', 'friend.friendId')->orderBy('created_at', 'DESC')->get(['votes.*']);
$friends_comments = $user->friends()->join('comments', 'comments.userId', '=', 'friend.friendId')->orderBy('created_at', 'DESC')->get(['comments.*']);
$friends_status = $user->friends()->join('status', 'status.userId', '=', 'friend.friendId')->orderBy('created_at', 'DESC')->get(['status.*']);

Mais je voudrais qu'ils arrivent tous en même temps, c'est parce que mysql trie des milliers d'enregistrements dans l'ordre est 100x plus rapide que php prenant 3 listes, les fusionnant puis les faisant. Des idées?

21
CMOS

Je suis sûr qu'il existe d'autres moyens d'y parvenir, mais une solution serait d'utiliser join via le Générateur de requêtes.

Si vous avez des tables, installez quelque chose comme ceci:

users
    id
    ...

friends
    id
    user_id
    friend_id
    ...

votes, comments and status_updates (3 tables)
    id
    user_id
    ....

Dans votre utilisateur modèle:

class User extends Eloquent {
    public function friends()
    {
        return $this->hasMany('Friend');
    }
}

Dans votre ami modèle:

class Friend extends Eloquent {
    public function user()
    {
        return $this->belongsTo('User');
    }
}

Ensuite, pour rassembler tous les votes pour les amis de l'utilisateur avec l'id de 1, vous pouvez lancer la requête suivante:

$user = User::find(1);
$friends_votes = $user->friends()
    ->with('user') // bring along details of the friend
    ->join('votes', 'votes.user_id', '=', 'friends.friend_id')
    ->get(['votes.*']); // exclude extra details from friends table

Exécutez le même join pour les comments et status_updates les tables. Si vous souhaitez que les votes, commentaires et status_updates fassent partie d'une seule liste chronologique, vous pouvez fusionner les trois collections résultantes en une seule, puis trier la collection fusionnée.


Modifier

Pour obtenir des votes, des commentaires et des mises à jour de statut dans une requête, vous pouvez créer chaque requête, puis fusionner les résultats. Malheureusement, cela ne semble pas fonctionner si nous utilisons la relation Eloquent hasMany ( voir les commentaires pour cette question pour une discussion de ce problème). Nous devons donc modifier les requêtes à utiliser where à la place:

$friends_votes = 
    DB::table('friends')->where('friends.user_id','1')
    ->join('votes', 'votes.user_id', '=', 'friends.friend_id');

$friends_comments = 
    DB::table('friends')->where('friends.user_id','1')
    ->join('comments', 'comments.user_id', '=', 'friends.friend_id');

$friends_status_updates = 
    DB::table('status_updates')->where('status_updates.user_id','1')
    ->join('friends', 'status_updates.user_id', '=', 'friends.friend_id');

$friends_events = 
    $friends_votes
    ->union($friends_comments)
    ->union($friends_status_updates)
    ->get();

À ce stade, cependant, notre requête devient un peu poilue, donc une relation polymorphe avec une table supplémentaire (comme le suggère DefiniteIntegral ci-dessous) pourrait être une meilleure idée.

34
damiani

Ce n'est probablement pas ce que vous voulez entendre, mais une table de "flux" constituerait un excellent intermédiaire pour ce type de transaction, car elle vous donnerait un moyen non standard de faire pivoter toutes ces données avec une relation polymorphe.

Vous pouvez le construire comme ceci:

<?php

Schema::create('feeds', function($table) {
    $table->increments('id');
    $table->timestamps();
    $table->unsignedInteger('user_id');
    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    $table->morphs('target'); 
});

Construisez le modèle de flux comme ceci:

<?php

class Feed extends Eloquent
{
    protected $fillable = ['user_id', 'target_type', 'target_id'];

    public function user()
    {
        return $this->belongsTo('User');
    }

    public function target()
    {
        return $this->morphTo();
    }
}

Puis maintenez-le à jour avec quelque chose comme:

<?php

Vote::created(function(Vote $vote) {
    $target_type = 'Vote';
    $target_id   = $vote->id;
    $user_id     = $vote->user_id;

    Feed::create(compact('target_type', 'target_id', 'user_id'));
});

Vous pouvez rendre ce qui précède beaucoup plus générique/robuste - ceci est juste à des fins de démonstration.

À ce stade, vos éléments de flux sont vraiment faciles à récupérer en une seule fois:

<?php

Feed::whereIn('user_id', $my_friend_ids)
    ->with('user', 'target')
    ->orderBy('created_at', 'desc')
    ->get();
3
DefiniteIntegral