Como obter valores distintos para campos de coluna não-chave no Laravel?

95

Isso pode ser muito fácil, mas não tenho ideia de como.

Eu tenho uma tabela que pode ter valores repetidos para um determinado campo de coluna não chave. Como faço para escrever uma consulta SQL usando o Query Builder ou Eloquent que irá buscar linhas com valores distintos para essa coluna?

Observe que não estou buscando apenas essa coluna, ela está em conjunto com outros valores de coluna, portanto, distinct()pode não funcionar realmente. Então, essa questão basicamente pode ser como especificar a coluna que desejo que seja distinta em uma consulta agora que distinct()não aceita parâmetros?

gthuo
fonte

Respostas:

121

Você deve usar groupby. No Query Builder, você pode fazer isso desta forma:

$users = DB::table('users')
            ->select('id','name', 'email')
            ->groupBy('name')
            ->get();
Marcin Nabiałek
fonte
2
Tenho uma tabela de "mensagens" (usada para um bate-papo) e desejo obter a mensagem mais recente que o usuário autenticado recebeu de cada conversa. Usando groupBy, posso obter apenas uma mensagem, mas recebo a primeira e preciso da última. Parece que orderBy não funciona.
JCarlosR
10
se você não quiser aplicar nenhuma operação matemática como SUM, AVG, MAX e assim por diante, "agrupar por" não é a resposta correta (você pode usá-la, mas não deve usá-la). Você deve usar distinto. No MySQL você pode usar DISTINCT para uma coluna e adicionar mais colunas ao conjunto de resultados: "SELECIONE DISTINCT nome, id, e-mail dos usuários". Com o eloquent, você pode fazer isso com $ this-> select (['nome', 'id', 'email']) -> distinto () -> get ();
Sergi
2
quando adiciono um, where()ele quebra, como posso consertar isso?
tq
1
quando eu uso isso, mostra 'id' e 'email' não está em groupBy (). Eu preciso usar groupBy ('id', 'nome', 'email'). O que não é útil.
Haque
1
Algo a se notar: group bynão é o mesmo que distinct. group bymudará o comportamento das funções agregadas, como count()dentro da consulta SQL. distinctnão mudará o comportamento de tais funções, portanto, você obterá resultados diferentes dependendo de qual usar.
Skeets
80

No Eloquent, você também pode consultar desta forma:

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

Pathros
fonte
12
Pergunta especificamente feitaNote that I am not fetching that column only, it is in conjunction with other column values
Hirnhamster
12
@Hirnhamster: Okkei. No entanto, esta resposta ainda é útil para outras pessoas que passam por ...
Pathros
3
Não é útil para mim, estou procurando especificamente o que é o OP. Não é um achado fácil.
blamb
1
$users = User::select('column1', 'column2', 'column3')->distinct()->get();
Mark-Hero
Você também precisa ->orderBy('name')se quiser usar isso com chunk.
Chibueze Opata
27

em eloquente, você pode usar isso

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

groupBy está, na verdade, buscando os valores distintos; na verdade, o groupBy categorizará os mesmos valores, para que possamos usar funções de agregação neles. mas neste cenário não temos funções agregadas, estamos apenas selecionando o valor que fará com que o resultado tenha valores distintos

Salar
fonte
4
Como é que isso funciona? O groupBy está realmente buscando nomes de usuário distintos? Você poderia explicar um pouco mais como isso responde ao problema da operação?
Félix Gagnon-Grenier
Sim, groupBy está realmente buscando os valores distintos, na verdade, o groupBy irá categorizar os mesmos valores, para que possamos usar funções de agregação neles. mas neste cenário não temos funções agregadas, estamos apenas selecionando o valor que fará com que o resultado tenha valores distintos.
Salar
Entendo ... então, como essa resposta difere da de Marcin? Ter uma resposta diferente está ok, contanto que você possa explicar como é diferente e que falha ela resolve :) Não se preocupe em responder a comentários, por favor, edite sua pergunta diretamente com as informações relevantes.
Félix Gagnon-Grenier
1
o resultado é o mesmo, depende do seu projeto usar ORM ou usar o construtor de consultas, se você estiver usando um deles, então é melhor ficar com aquele. é por isso que respondi sua pergunta de uma maneira diferente.
Salar
Bem, eu tenho esse problema em que, se você quiser verificar se um item existe usando a in_array()função, ele nunca funciona. Para consertar, tentei ->lists()(versão 5.2). Então, $users = User::select('name')->groupBy('name')->lists('name');funcionou bem para php's in_array();
Pathros
20

Embora esteja atrasado para responder, uma abordagem melhor para obter registros distintos usando o Eloquent seria

$user_names = User::distinct()->get(['name']);
Rsakhale
fonte
Ok, você agregou valor ao nos mostrar que também é possível passar parâmetros de nomes de campo para o get()método (e eu testei também para o first()método), o que é equivalente a usar o select()método mostrado em algumas respostas aqui. Embora de alguma forma groupByainda pareça ter a pontuação mais alta. No entanto, isso seria verdadeiramente distinto se essa for a única coluna que estou selecionando.
gthuo
Em termos de desempenho, distinto vai pontuar sobre groupBy, porque os registros serão selecionados primeiro no SQL Engine, ao contrário do Group By, o mecanismo primeiro seleciona todos os registros e depois agrupa por ..
rsakhale
1
$user_names = User::distinct()->count(['name']);também funciona
Bira
12

O agrupamento por não funcionará se as regras do banco de dados não permitirem que nenhum dos campos selecionados fique fora de uma função de agregação. Em vez disso, use as coleções laravel .

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

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

Alguém observou que isso pode não ser ótimo no desempenho de grandes coleções. Eu recomendaria adicionar uma chave à coleção. O método a ser usado é chamado de keyBy . Este é o método simples.

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

O keyBy também permite adicionar uma função de retorno de chamada para coisas mais complexas ...

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

Se você tiver que iterar em grandes coleções, adicionar uma chave para resolver o problema de desempenho.

Jed Lynch
fonte
Você está correto e é exatamente o problema que estou enfrentando ... no entanto, esperar até que a consulta seja executada e executar a ação na coleção também não é o ideal. Em particular, se você dependesse da paginação, sua operação seria após o cálculo da paginação e os itens por página estariam errados. Além disso, o banco de dados é mais adequado para operar em grandes blocos de dados em vez de recuperá-los e processá-los no código. Para pequenos blocos de dados, seria um problema
menor
Você tem um bom argumento. Este método pode não ter um ótimo desempenho em grandes coleções e não funcionará para paginação. Acho que há uma resposta melhor do que a que eu tenho.
Jed Lynch
7

**

Testado para Laravel 5.8

**

Como você deseja obter todas as colunas da tabela, pode coletar todos os dados e filtrá-los usando a função de coleções chamada Unique

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

ou

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

Para mais informações, você pode verificar Documentação da Coleção Laravel

EDITAR: Você pode ter problemas com o desempenho, usando Unique () você obterá todos os dados primeiro da tabela do usuário, e então o Laravel irá filtrá-los. Isso não é bom se você tiver muitos dados de usuários. Você pode usar o construtor de consultas e chamar cada campo que deseja usar, por exemplo:

User::select('username','email','name')->distinct('name')->get();
Robby Alvian Jaya Mulia
fonte
Isso não é eficiente, pois você obtém todos os dados primeiro do banco de dados
Razor Mureithi
É por isso que é chamado de filtragem, você pode usar distintos () para o construtor de consultas, mas você não pode obter outros campos (de qualquer forma, você ainda pode chamar cada um dos campos por meio de select). Usar unique () é a única maneira de obter todos os campos sem chamar cada um deles (embora saibamos que pode haver problemas com o desempenho).
Robby Alvian Jaya Mulia
6

Observe que groupBy como usado acima, não funcionará para postgres.

Usando distinct é provavelmente a melhor opção - por exemplo $users = User::query()->distinct()->get();

Se você usar, querypoderá selecionar todas as colunas conforme solicitado.

jec006
fonte
5

$users = User::select('column1', 'column2', 'column3')->distinct()->get();recupera todos os três canais para linhas distintas na tabela. Você pode adicionar quantas colunas desejar.

Mark-Hero
fonte
3

Achei esse método funcionando muito bem (para mim) para produzir uma matriz plana de valores únicos:

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

Se você executou ->toSql()neste construtor de consulta, verá que ele gera uma consulta como esta:

select distinct `name` from `users`

O ->pluck()é tratado por illuminate \ collection lib (não via consulta sql).

Latheesan
fonte
1

Tive os mesmos problemas ao tentar preencher uma lista de todos os tópicos exclusivos que um usuário teve com outros usuários. Isso funcionou para mim

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
fonte
0
$users = Users::all()->unique('name');
Roland Verner
fonte
5
Explique COMO seu trecho de código resolve o problema do OP.
ifconfig
0

Pra quem gosta de mim cometendo o mesmo erro. Aqui está a resposta elaborada Testado no Laravel 5.7

A. Registros no banco de dados

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 desejado

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

Portanto, passe apenas aquelas colunas "select()", cujos valores são iguais. Por exemplo: 'type','url'. Você pode adicionar mais colunas, desde que tenham o mesmo valor 'updated_at'.

Se você tentar passar "created_at"ou "id"no "select()", então você vai obter os registros mesmo que A. Porque eles são diferentes para cada linha no DB.

Santosh Kumar
fonte
0
// Get unique value for table 'add_new_videos' column name 'project_id'
$project_id = DB::table('add_new_videos')->distinct()->get(['project_id']);
Nazmul Haque
fonte
-1
$users = User::all();
foreach($users->unique('column_name') as $user){
    code here..
}
Mrunali Khandekar
fonte