Comment obtenir des valeurs distinctes pour les champs de colonne non clés dans Laravel?

94

Cela peut être assez facile mais je ne sais pas comment le faire.

J'ai une table qui peut avoir des valeurs répétées pour un champ de colonne non clé particulier. Comment écrire une requête SQL à l'aide de Query Builder ou Eloquent qui récupérera des lignes avec des valeurs distinctes pour cette colonne?

Notez que je ne récupère pas uniquement cette colonne, elle est en conjonction avec d'autres valeurs de colonne, donc distinct()peut ne pas vraiment fonctionner. Donc, cette question peut essentiellement être de savoir comment spécifier la colonne que je veux être distincte dans une requête qui distinct()n'accepte aucun paramètre?

gthuo
la source

Réponses:

115

Vous devriez utiliser groupby. Dans Query Builder, vous pouvez le faire de cette façon:

$users = DB::table('users')
            ->select('id','name', 'email')
            ->groupBy('name')
            ->get();
Marcin Nabiałek
la source
2
J'ai une table "messages" (utilisée pour un chat), et je veux obtenir le dernier message que l'utilisateur authentifié a reçu de chaque conversation. En utilisant groupBy, je ne peux recevoir qu'un seul message, mais je reçois le premier et j'ai besoin du dernier message. Il semble que orderBy ne fonctionne pas.
JCarlosR
10
si vous ne voulez pas appliquer d'opérations mathématiques telles que SUM, AVG, MAX et ainsi de suite, "grouper par" n'est pas la bonne réponse (vous pouvez l'utiliser, mais vous ne devriez pas l'utiliser). Vous devez utiliser distinct. Dans MySQL, vous pouvez utiliser DISTINCT pour une colonne et ajouter plus de colonnes au jeu de résultats: "SELECT DISTINCT nom, id, email FROM utilisateurs". Avec éloquent, vous pouvez le faire avec $ this-> select (['name', 'id', 'email']) -> distinct () -> get ();
Sergi
1
lorsque j'ajoute un where()problème, comment puis-je résoudre ce problème?
tq
1
quand j'utilise ceci, il montre que «id» et «email» n'est pas dans groupBy () j'ai besoin d'utiliser groupBy («id», «name», «email»). Ce qui n'est pas utile.
Haque
1
Quelque chose à noter: ce group byn'est pas la même chose que distinct. group bychangera le comportement des fonctions d'agrégation comme count()dans la requête SQL. distinctne changera pas le comportement de ces fonctions, vous obtiendrez donc des résultats différents selon celle que vous utilisez.
Skeets
77

Dans Eloquent, vous pouvez également interroger comme ceci:

$users = User::select('name')->distinct()->get();

Pathros
la source
12
Question Note that I am not fetching that column only, it is in conjunction with other column values
posée
12
@Hirnhamster: Okkei. Cependant, cette réponse est toujours utile pour les autres qui passent ...
Pathros
3
Pas utile pour moi, je regarde spécifiquement ce qu'est l'OP. Ce n'est pas une trouvaille facile.
blamb
1
$users = User::select('column1', 'column2', 'column3')->distinct()->get();
Mark-Hero
Vous en aurez également besoin ->orderBy('name')si vous souhaitez l'utiliser avec chunk.
Chibueze Opata le
26

avec éloquence, vous pouvez utiliser ceci

$users = User::select('name')->groupBy('name')->get()->toArray() ;

groupBy récupère en fait les valeurs distinctes, en fait, groupBy catégorisera les mêmes valeurs, afin que nous puissions utiliser des fonctions d'agrégation sur elles. mais dans ce scénario, nous n'avons pas de fonctions d'agrégation, nous sélectionnons simplement la valeur qui donnera au résultat des valeurs distinctes

Salar
la source
4
Comment cela marche-t-il? Le groupBy récupère-t-il réellement des noms d'utilisateurs distincts? Pourriez-vous s'il vous plaît expliquer un peu plus comment cela répond au problème de l'op?
Félix Gagnon-Grenier
Oui, groupBy récupère en fait les valeurs distinctes, en fait, groupBy catégorisera les mêmes valeurs, afin que nous puissions utiliser des fonctions d'agrégation sur elles. mais dans ce scénario, nous n'avons pas de fonctions d'agrégation, nous sélectionnons simplement la valeur qui donnera au résultat des valeurs distinctes.
Salar
Je vois… alors, en quoi cette réponse est-elle différente de celle de Marcin? Avoir une réponse différente est ok, tant que vous pouvez expliquer en quoi elle est différente et quel défaut elle résout :) Ne vous embêtez pas à répondre dans les commentaires, veuillez modifier directement votre question avec les informations pertinentes.
Félix Gagnon-Grenier
1
le résultat est le même, cela dépend de votre projet d'utiliser ORM ou d'utiliser le générateur de requêtes, si vous en utilisez un, il vaut mieux s'en tenir à celui-là. c'est pourquoi j'ai répondu à votre question d'une manière différente.
Salar
Eh bien, j'ai ce problème où, si vous voulez maintenant vérifier si un élément existe en utilisant la in_array()fonction, cela ne fonctionne jamais. Pour y remédier, j'ai essayé à la ->lists()place (version 5.2). Donc, $users = User::select('name')->groupBy('name')->lists('name');a bien fonctionné pour php in_array();
Pathros
19

Bien que je sois en retard pour répondre à cette question, une meilleure approche pour obtenir des enregistrements distincts en utilisant Eloquent serait

$user_names = User::distinct()->get(['name']);
rsakhale
la source
Ok, vous avez ajouté de la valeur en nous montrant que l'on peut également passer des paramètres de noms de champs à la get()méthode (et j'ai testé aussi à la first()méthode), ce qui équivaut à utiliser la select()méthode comme indiqué dans quelques réponses ici. Bien que d'une manière ou d'une autre groupBysemble toujours marquer le plus haut score. Cependant, ce serait vraiment distinct si c'est la seule colonne que je sélectionne.
gthuo
En termes de performances, distinct va marquer par rapport à groupBy, car les enregistrements seront d'abord sélectionnés dans le moteur SQL, contrairement à Group By, le moteur sélectionne d'abord tous les enregistrements, puis group's by ..
rsakhale
1
$user_names = User::distinct()->count(['name']);fonctionne aussi
Bira le
11

Le regroupement par ne fonctionnera pas si les règles de la base de données n'autorisent aucun des champs de sélection à être en dehors d'une fonction d'agrégation. Utilisez plutôt les collections laravel .

$users = DB::table('users')
        ->select('id','name', 'email')
        ->get();

foreach($users->unique('name') as $user){
  //....
}

Quelqu'un a souligné que cela peut ne pas être très performant pour les grandes collections. Je recommanderais d'ajouter une clé à la collection. La méthode à utiliser est appelée keyBy . C'est la méthode simple.

     $users = DB::table('users')
        ->select('id','name', 'email')
        ->get()
        ->keyBy('name');

Le keyBy vous permet également d'ajouter une fonction de rappel pour des choses plus complexes ...

     $users = DB::table('users')
        ->select('id','name', 'email')
        ->get()
        ->keyBy(function($user){
              return $user->name . '-' . $user->id;
         });

Si vous devez parcourir de grandes collections, l'ajout d'une clé résout le problème de performances.

Jed Lynch
la source
Vous avez raison et c'est exactement le problème auquel je suis confronté ... mais attendre que la requête soit exécutée et effectuer l'action dans la collection n'est pas non plus idéal. En particulier si vous vous fiez à la pagination, votre opération se ferait après le calcul de la pagination et les éléments par page seraient erronés. En outre, la base de données est mieux adaptée pour fonctionner sur des blocs de données volumineux plutôt que pour les récupérer et les traiter dans le code. Pour les petits morceaux de données, ce serait moins un problème
ChronoFish
Vous avez un bon point. Cette méthode peut ne pas être très performante sur les grandes collections, et cela ne fonctionnera pas pour la pagination. Je pense qu'il y a une meilleure réponse que celle que j'ai.
Jed Lynch
6

Notez que groupBytel qu'utilisé ci-dessus ne fonctionnera pas pour les postgres.

L'utilisation distinctest probablement une meilleure option - par exemple $users = User::query()->distinct()->get();

Si vous utilisez, queryvous pouvez sélectionner toutes les colonnes comme demandé.

jec006
la source
6

**

Testé pour Laravel 5.8

**

Puisque vous voulez obtenir toutes les colonnes de la table, vous pouvez collecter toutes les données, puis les filtrer à l'aide de la fonction Collections appelée Unique

// Get all users with unique name
User::all()->unique('name')

ou

// Get all & latest users with unique name 
User::latest()->get()->unique('name')

Pour plus d'informations, vous pouvez consulter les documentations de la collection Laravel

EDIT: Vous pourriez avoir un problème avec les performances, en utilisant Unique (), vous obtiendrez d'abord toutes les données de la table User, puis Laravel les filtrera. Cette méthode n'est pas bonne si vous avez beaucoup de données d'utilisateurs. Vous pouvez utiliser le générateur de requêtes et appeler chaque champ que vous souhaitez utiliser, par exemple:

User::select('username','email','name')->distinct('name')->get();
Robby Alvian Jaya Mulia
la source
Ce n'est pas efficace car vous obtenez d'abord toutes les données de db
Razor Mureithi
C'est pourquoi cela s'appelle le filtrage, vous pouvez utiliser distinct () pour le générateur de requêtes, mais vous ne pouvez pas obtenir d'autres champs (de toute façon, vous pouvez toujours appeler chacun des champs via select). L'utilisation de unique () est le seul moyen d'obtenir tous les champs sans appeler chacun d'entre eux (bien que nous sachions que cela pourrait avoir un problème avec les performances).
Robby Alvian Jaya Mulia le
5

$users = User::select('column1', 'column2', 'column3')->distinct()->get();récupère les trois coulmns pour des lignes distinctes dans le tableau. Vous pouvez ajouter autant de colonnes que vous le souhaitez.

Mark-Hero
la source
3

J'ai trouvé que cette méthode fonctionnait assez bien (pour moi) pour produire un tableau plat de valeurs uniques:

$uniqueNames = User::select('name')->distinct()->pluck('name')->toArray();

Si vous avez exécuté ->toSql()ce générateur de requêtes, vous verrez qu'il génère une requête comme celle-ci:

select distinct `name` from `users`

Le ->pluck()est géré par illuminate \ collection lib (pas via une requête SQL).

Latheesan
la source
1

J'ai eu les mêmes problèmes en essayant de remplir une liste de tous les threads uniques qu'un utilisateur avait avec d'autres utilisateurs. Cela a fait l'affaire pour moi

Message::where('from_user', $user->id)
        ->select(['from_user', 'to_user'])
        ->selectRaw('MAX(created_at) AS last_date')
        ->groupBy(['from_user', 'to_user'])
        ->orderBy('last_date', 'DESC')
        ->get()
Alex Christodoulou
la source
0
$users = Users::all()->unique('name');
Roland Verner
la source
5
Veuillez expliquer COMMENT votre extrait de code résout le problème de l'OP.
ifconfig
0

Pour ceux qui comme moi font la même erreur. Voici la réponse élaborée Testé dans Laravel 5.7

A. Enregistrements dans DB

UserFile :: orderBy ('created_at', 'desc') -> get () -> toArray ();

Array
(
    [0] => Array
        (
            [id] => 2073
            [type] => 'DL'
            [url] => 'https://i.picsum.photos/12/884/200/300.jpg'
            [created_at] => 2020-08-05 17:16:48
            [updated_at] => 2020-08-06 18:08:38
        )

    [1] => Array
        (
            [id] => 2074
            [type] => 'PROFILE'
            [url] => 'https://i.picsum.photos/13/884/200/300.jpg'
            [created_at] => 2020-08-05 17:20:06
            [updated_at] => 2020-08-06 18:08:38
        )

    [2] => Array
        (
            [id] => 2076
            [type] => 'PROFILE'
            [url] => 'https://i.picsum.photos/13/884/200/300.jpg'
            [created_at] => 2020-08-05 17:22:01
            [updated_at] => 2020-08-06 18:08:38
        )

    [3] => Array
        (
            [id] => 2086
            [type] => 'PROFILE'
            [url] => 'https://i.picsum.photos/13/884/200/300.jpg'
            [created_at] => 2020-08-05 19:22:41
            [updated_at] => 2020-08-06 18:08:38
        )
)

B. Résultat groupé souhaité

UserFile :: select ('type', 'url', 'updated_at) -> distinct (' type ') -> get () -> toArray ();

Array
(
    [0] => Array
        (
            [type] => 'DL'
            [url] => 'https://i.picsum.photos/12/884/200/300.jpg'
            [updated_at] => 2020-08-06 18:08:38 
        )

    [1] => Array
        (
            [type] => 'PROFILE'
            [url] => 'https://i.picsum.photos/13/884/200/300.jpg'
            [updated_at] => 2020-08-06 18:08:38
        )
)

Alors ne transmettez que les colonnes "select()"dont les valeurs sont identiques. Par exemple: 'type','url'. Vous pouvez ajouter d'autres colonnes à condition qu'elles aient la même valeur que 'updated_at'.

Si vous essayez de passer "created_at"ou "id"dans "select()", alors vous obtiendrez les mêmes que les enregistrements A. Parce qu'ils sont différents pour chaque ligne de DB.

Santosh Kumar
la source
-1
$users = User::all();
foreach($users->unique('column_name') as $user){
    code here..
}
Mrunali Khandekar
la source