Come ottenere valori distinti per i campi delle colonne non chiave in Laravel?


94

Potrebbe essere abbastanza facile ma non hai idea di come farlo.

Ho una tabella che può avere valori ripetuti per un particolare campo di colonna non chiave. Come si scrive una query SQL utilizzando Query Builder o Eloquent che recupera righe con valori distinti per quella colonna?

Nota che non sto recuperando solo quella colonna, è in combinazione con altri valori di colonna, quindi distinct()potrebbe non funzionare davvero. Quindi quella domanda fondamentalmente può essere come specificare la colonna che voglio essere distinta in una query ora che distinct()non accetta parametri?

Risposte:


115

Dovresti usare groupby. In Query Builder puoi farlo in questo modo:

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

2
Ho una tabella "messaggi" (usata per una chat) e desidero ricevere l'ultimo messaggio che l'utente autenticato ha ricevuto da ogni conversazione. Utilizzando groupBy posso ricevere un solo messaggio, ma ricevo il primo e ho bisogno dell'ultimo messaggio. Sembra che orderBy non funzioni.
JCarlosR

10
se non vuoi applicare alcuna operazione matematica come SOMMA, AVG, MAX e così via "raggruppa per" non è la risposta corretta (puoi usarla, ma non dovresti usarla). Dovresti usare distinto. In MySQL puoi usare DISTINCT per una colonna e aggiungere più colonne al gruppo di risultati: "SELECT DISTINCT name, id, email FROM users". Con eloquent puoi farlo con $ this-> select (['name', 'id', 'email']) -> separate () -> get ();
Sergi

1
quando aggiungo una where()si rompe, come posso risolverlo?
tq

1
quando lo uso mostra 'id' e 'email' non è in groupBy () devo usare groupBy ('id', 'name', 'email'). Che non è utile.
Haque

1
Qualcosa da notare: group bynon è lo stesso di distinct. group bycambierà il comportamento delle funzioni aggregate come count()all'interno della query SQL. distinctnon cambierà il comportamento di tali funzioni, quindi si ottengono risultati diversi a seconda di quale si utilizza.
Skeets

77

In Eloquent puoi anche eseguire query in questo modo:

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


12
Domanda specificaNote that I am not fetching that column only, it is in conjunction with other column values
Hirnhamster

12
@Hirnhamster: Okkei. Tuttavia, questa risposta è ancora utile per gli altri di passaggio ...
Pathros

3
Non utile per me, sto cercando in particolare cosa sia l'OP. Non è una scoperta facile.
blamb

1
$users = User::select('column1', 'column2', 'column3')->distinct()->get();
Mark-Hero

Inoltre è necessario ->orderBy('name')se si desidera utilizzare questo file chunk.
Chibueze Opata

26

in eloquente puoi usare questo

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

groupBy sta effettivamente recuperando i valori distinti, infatti groupBy classificherà gli stessi valori, in modo che possiamo utilizzare funzioni di aggregazione su di essi. ma in questo scenario non abbiamo funzioni aggregate, stiamo solo selezionando il valore che farà sì che il risultato abbia valori distinti


4
Come funziona? GroupBy sta effettivamente recuperando nomi utente distinti? Potresti spiegare un po 'di più come questo risolve il problema dell'operatore?
Félix Gagnon-Grenier

Sì, groupBy sta effettivamente recuperando i valori distinti, infatti groupBy classificherà gli stessi valori, in modo che possiamo utilizzare funzioni di aggregazione su di essi. ma in questo scenario non abbiamo funzioni aggregate, stiamo solo selezionando il valore che farà sì che il risultato abbia valori distinti.
Salar

Capisco .. allora, in che modo questa risposta è diversa da quella di Marcin? Avere una risposta diversa va bene, purché tu possa spiegare come è diversa e quale difetto risolve :) Non preoccuparti di rispondere nei commenti, modifica direttamente la tua domanda con le informazioni pertinenti.
Félix Gagnon-Grenier

1
il risultato è lo stesso, dipende dal tuo progetto utilizzare ORM o utilizzare il generatore di query, se ne stai usando uno, allora è meglio attenersi a quello. ecco perché ho risposto alla tua domanda in modo diverso.
Salar

Bene, ho questo problema in cui, se ora vuoi controllare se un elemento esiste utilizzando la in_array()funzione, non funziona mai. Per risolverlo, ho provato ->lists()invece (Versione 5.2). Quindi, ha $users = User::select('name')->groupBy('name')->lists('name');funzionato bene per php in_array();
Pathros

19

Anche se sono in ritardo per rispondere a questa domanda, sarebbe un approccio migliore per ottenere record distinti utilizzando Eloquent

$user_names = User::distinct()->get(['name']);

Ok, hai aggiunto valore mostrandoci che si possono anche passare i parametri dei nomi dei campi al get()metodo (e ho provato anche al first()metodo), il che equivale a utilizzare il select()metodo come mostrato in alcune risposte qui. Anche se in qualche modo groupBysembra ancora ottenere il punteggio più alto. Tuttavia questo sarebbe veramente distinto se questa è l'unica colonna che sto selezionando.
gthuo

Dal punto di vista delle prestazioni, distinguerà il punteggio rispetto al gruppo By, perché i record verranno selezionati inizialmente nel motore SQL a differenza di Group By, il motore seleziona prima tutti i record e poi quelli del gruppo ..
rsakhale

1
$user_names = User::distinct()->count(['name']);funziona anche
Bira

11

Il raggruppamento per non funzionerà se le regole del database non consentono a nessuno dei campi selezionati di essere al di fuori di una funzione di aggregazione. Usa invece le collezioni di laravel .

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

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

Qualcuno ha sottolineato che questo potrebbe non essere eccezionale in termini di prestazioni per grandi raccolte. Consiglierei di aggiungere una chiave alla raccolta. Il metodo da utilizzare si chiama keyBy . Questo è il metodo semplice.

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

Il keyBy ti consente anche di aggiungere una funzione di richiamata per cose più complesse ...

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

Se devi iterare su raccolte di grandi dimensioni, l'aggiunta di una chiave risolve il problema delle prestazioni.


Hai ragione ed è esattamente il problema che sto affrontando ... tuttavia anche aspettare che la query venga eseguita ed eseguire l'azione nella raccolta non è l'ideale. In particolare, se ti affidassi all'impaginazione, la tua operazione sarebbe dopo il calcolo dell'impaginazione e gli elementi per pagina sarebbero sbagliati. Inoltre, il DB è più adatto per operare su grandi blocchi di dati piuttosto che recuperarli ed elaborarli in codice. Per piccoli blocchi di dati sarebbe meno un problema
ChronoFish

Hai un buon punto. Questo metodo potrebbe non essere ottimo per quanto riguarda le prestazioni su raccolte di grandi dimensioni e non funzionerà per l'impaginazione. Penso che ci sia una risposta migliore di quella che ho.
Jed Lynch

6

Nota che groupBy come usato sopra non funzionerà per postgres.

Utilizzando distinct è probabilmente un'opzione migliore, ad es $users = User::query()->distinct()->get();

Se si utilizza queryè possibile selezionare tutte le colonne come richiesto.


6

**

Testato per Laravel 5.8

**

Dal momento che vuoi ottenere tutte le colonne dalla tabella, puoi raccogliere tutti i dati e quindi filtrarli utilizzando la funzione Collections chiamata Unique

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

o

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

Per ulteriori informazioni puoi controllare documentazione della collezione Laravel

EDIT: potresti avere problemi con le prestazioni, usando Unique () otterrai prima tutti i dati dalla tabella utente, quindi Laravel li filtrerà. In questo modo non va bene se hai molti dati degli utenti. Puoi utilizzare il generatore di query e chiamare ogni campo che desideri utilizzare, ad esempio:

User::select('username','email','name')->distinct('name')->get();

Questo non è efficiente poiché ottieni tutti i dati prima da db
Razor Mureithi il

Questo è il motivo per cui si chiama filtraggio, puoi usare distinte () per il generatore di query ma non puoi ottenere altri campi (comunque puoi ancora chiamare ogni campo tramite select). L'utilizzo di unique () è l'unico modo per ottenere tutti i campi senza chiamare ciascuno di essi (sebbene sappiamo che potrebbe avere problemi con le prestazioni).
Robby Alvian Jaya Mulia il

5

$users = User::select('column1', 'column2', 'column3')->distinct()->get();recupera tutte e tre le coulmns per righe distinte nella tabella. Puoi aggiungere tutte le colonne che desideri.


3

Ho trovato questo metodo funzionante abbastanza bene (per me) per produrre una matrice piatta di valori univoci:

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

Se hai eseguito ->toSql()questo generatore di query, vedrai che genera una query come questa:

select distinct `name` from `users`

Il ->pluck()è gestita da illuminare collezione lib \ (non tramite query SQL).


1

Ho riscontrato gli stessi problemi durante il tentativo di compilare un elenco di tutti i thread univoci che un utente aveva con altri utenti. Questo ha funzionato per me

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


0

Per chi come me fa lo stesso errore. Ecco la risposta elaborata Testata in Laravel 5.7

A. Record in 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. Risultato raggruppato desiderato

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

Quindi Passa solo quelle colonne "select()", i cui valori sono gli stessi. Ad esempio: 'type','url'. Puoi aggiungere più colonne a condizione che abbiano lo stesso valore come 'updated_at'.

Se si tenta di passare "created_at"o "id"in "select()", allora si ottiene il record stesso di A. Perché sono diversi per ogni riga nel DB.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.