¿Cómo obtener valores distintos para campos de columnas no clave en Laravel?

94

Esto puede ser bastante fácil, pero no tengo idea de cómo hacerlo.

Tengo una tabla que puede tener valores repetidos para un campo de columna no clave en particular. ¿Cómo escribo una consulta SQL usando Query Builder o Eloquent que obtendrá filas con valores distintos para esa columna?

Tenga en cuenta que no estoy obteniendo solo esa columna, está junto con otros valores de columna, por lo distinct()que es posible que no funcione. Entonces, esa pregunta básicamente puede ser cómo especificar la columna que quiero que sea distinta en una consulta ahora que distinct()no acepta parámetros.

gthuo
fuente

Respuestas:

116

Deberías usar groupby. En Query Builder puede hacerlo de esta manera:

$users = DB::table('users')
            ->select('id','name', 'email')
            ->groupBy('name')
            ->get();
Marcin Nabiałek
fuente
2
Tengo una tabla de "mensajes" (usada para un chat), y quiero obtener el último mensaje que el usuario autenticado ha recibido de cada conversación. Usando groupBy puedo obtener solo un mensaje, pero obtengo el primero y necesito el último mensaje. Parece que orderBy no funciona.
JCarlosR
10
si no desea aplicar ninguna operación matemática como SUM, AVG, MAX, etc. "agrupar por" no es la respuesta correcta (puede usarla, pero no debe usarla). Debería utilizar distinto. En MySQL puede usar DISTINCT para una columna y agregar más columnas al conjunto de resultados: "SELECT DISTINCT name, id, email FROM users". Con elocuente puede hacer esto con $ this-> select (['nombre', 'id', 'correo electrónico']) -> distinto () -> get ();
Sergi
1
cuando agrego un where()se rompe, ¿cómo puedo solucionarlo?
tq
1
cuando lo uso, muestra 'id' y 'email' no está en groupBy () necesito usar groupBy ('id', 'name', 'email'). Lo que no es útil.
Haque
1
Algo a tener en cuenta: group byno es lo mismo que distinct. group bycambiará el comportamiento de las funciones agregadas como count()dentro de la consulta SQL. distinctno cambiará el comportamiento de dichas funciones, por lo que obtendrá resultados diferentes dependiendo de cuál utilice.
Skeets
78

En Eloquent también puedes consultar así:

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

Pathros
fuente
12
La pregunta pregunta específicamenteNote that I am not fetching that column only, it is in conjunction with other column values
Hirnhamster
12
@Hirnhamster: Okkei. Sin embargo, esta respuesta sigue siendo útil para otros que pasan ...
Pathros
3
No es útil para mí, estoy buscando específicamente qué es el OP. No es fácil de encontrar.
culpa el
1
$users = User::select('column1', 'column2', 'column3')->distinct()->get();
Mark-Hero
También necesitas ->orderBy('name')si quieres usar esto con chunk.
Chibueze Opata
26

en elocuente puedes usar esto

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

groupBy en realidad está obteniendo los valores distintos, de hecho groupBy categorizará los mismos valores, de modo que podamos usar funciones agregadas en ellos. pero en este escenario no tenemos funciones agregadas, solo estamos seleccionando el valor que hará que el resultado tenga valores distintos

Salar
fuente
4
¿Como funciona esto? ¿El groupBy está obteniendo nombres de usuario distintos? ¿Podría explicar un poco más cómo esto responde al problema de la operación?
Félix Gagnon-Grenier
Sí, groupBy en realidad está obteniendo los valores distintos, de hecho, groupBy categorizará los mismos valores, de modo que podamos usar funciones agregadas en ellos. pero en este escenario no tenemos funciones agregadas, solo estamos seleccionando el valor que hará que el resultado tenga valores distintos.
Salar
Ya veo ... entonces, ¿en qué se diferencia esta respuesta de la de Marcin? Tener una respuesta diferente está bien, siempre que pueda explicar en qué se diferencia y qué defecto soluciona :) No se moleste en responder en los comentarios, edite directamente su pregunta con información relevante.
Félix Gagnon-Grenier
1
el resultado es el mismo, depende de su proyecto para usar ORM o usar el generador de consultas, si está usando uno de ellos, entonces es mejor seguir con ese. por eso respondí a tu pregunta de otra manera.
Salar
Bueno, tengo este problema en el que, si ahora desea verificar si existe un elemento mediante el uso de la in_array()función, nunca funciona. Para solucionarlo, probé en su ->lists()lugar (Versión 5.2). Entonces, $users = User::select('name')->groupBy('name')->lists('name');funcionó bien para php in_array();
Pathros
19

Aunque llego tarde para responder a esto, un mejor enfoque para obtener registros distintos usando Eloquent sería

$user_names = User::distinct()->get(['name']);
rsakhale
fuente
Ok, ha agregado valor al mostrarnos que también se pueden pasar parámetros de nombres de campo al get()método (y también lo he probado first()), lo que equivale a usar el select()método como se muestra en algunas respuestas aquí. Aunque de alguna manera groupBytodavía parece tener la puntuación más alta. Sin embargo, esto sería realmente distinto si esa es la única columna que estoy seleccionando.
gthuo
En cuanto al rendimiento, distinto va a marcar más de GroupBy, debido a que los registros serán seleccionados en un primer momento en el motor SQL al contrario que en Agrupar por los primeros selecciona todos los registros del motor y luego por el grupo de ..
rsakhale
1
$user_names = User::distinct()->count(['name']);también funciona
Bira
11

La agrupación por no funcionará si las reglas de la base de datos no permiten que ninguno de los campos seleccionados esté fuera de una función agregada. En su lugar, use las colecciones de laravel .

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

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

Alguien señaló que esto puede no ser excelente para el rendimiento de colecciones grandes. Recomendaría agregar una clave a la colección. El método a utilizar se llama keyBy . Este es el método simple.

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

KeyBy también le permite agregar una función de devolución de llamada para cosas más complejas ...

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

Si tiene que iterar sobre colecciones grandes, agregarle una clave resuelve el problema de rendimiento.

Jed Lynch
fuente
Tiene razón y es exactamente el problema al que me enfrento ... sin embargo, esperar hasta que se ejecute la consulta y realizar la acción en la colección tampoco es ideal. En particular, si confiaba en la paginación, su operación sería posterior al cálculo de la paginación y los elementos por página serían incorrectos. Además, la base de datos es más adecuada para operar con grandes fragmentos de datos en lugar de recuperarlos y procesarlos en código. Para fragmentos de datos pequeños, sería un problema
menor
Tienes un buen punto. Este método puede no tener un gran rendimiento en colecciones grandes y no funcionará para la paginación. Creo que hay una mejor respuesta que la que tengo.
Jed Lynch
6

Tenga en cuenta que, groupBycomo se usó anteriormente, no funcionará para postgres.

Usar distinctes probablemente una mejor opción, p. Ej. $users = User::query()->distinct()->get();

Si lo usa query, puede seleccionar todas las columnas según lo solicitado.

jec006
fuente
6

**

Probado para Laravel 5.8

**

Como desea obtener todas las columnas de la tabla, puede recopilar todos los datos y luego filtrarlos usando la función Colecciones llamada Única

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

o

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

Para obtener más información, puede consultar la documentación de la colección Laravel

EDITAR: Es posible que tenga problemas con el rendimiento, al usar Unique () obtendrá todos los datos primero de la tabla de usuarios, y luego Laravel los filtrará. Esta forma no es buena si tiene muchos datos de usuarios. Puede usar el generador de consultas y llamar a cada campo que desee usar, ejemplo:

User::select('username','email','name')->distinct('name')->get();
Robby Alvian Jaya Mulia
fuente
Esto no es eficiente ya que obtiene todos los datos primero de db
Razor Mureithi
Es por eso que se llama filtrado, puede usar different () para el generador de consultas pero no puede obtener otros campos (de todos modos, aún puede llamar a cada campo a través de seleccionar). El uso de unique () es la única forma de obtener todos los campos sin llamar a cada uno de ellos (aunque sabemos que podría tener problemas con el rendimiento).
Robby Alvian Jaya Mulia
5

$users = User::select('column1', 'column2', 'column3')->distinct()->get();recupera los tres coulmns para filas distintas en la tabla. Puede agregar tantas columnas como desee.

Mark-Hero
fuente
3

Encontré que este método funciona bastante bien (para mí) para producir una matriz plana de valores únicos:

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

Si ejecutó ->toSql()este generador de consultas, verá que genera una consulta como esta:

select distinct `name` from `users`

El ->pluck()es manejado por illuminate \ collection lib (no a través de una consulta sql).

Latheesan
fuente
1

Tuve los mismos problemas al intentar completar una lista de todos los hilos únicos que un usuario tenía con otros usuarios. Esto me sirvió

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
fuente
0
$users = Users::all()->unique('name');
Roland Verner
fuente
5
Explique CÓMO su fragmento de código resuelve el problema del OP.
ifconfig
0

Para aquellos a los que les gusto cometiendo el mismo error. Aquí está la respuesta elaborada Probada en Laravel 5.7

A. Registros en 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. Resultado agrupado deseado

UserFile :: select ('tipo', 'url', 'updated_at) -> distinto (' tipo ') -> 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
        )
)

Así que pase solo aquellas columnas "select()"cuyos valores sean iguales. Por ejemplo: 'type','url'. Puede agregar más columnas siempre que tengan el mismo valor que 'updated_at'.

Si se intenta pasar "created_at"o "id"en "select()", por lo que recibirá los registros mismo que A. Debido a que son diferentes para cada fila en la base de datos.

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