Jak uzyskać różne wartości dla pól kolumn niebędących kluczami w Laravel?

96

Może to być dość łatwe, ale nie mam pojęcia, jak to zrobić.

Mam tabelę, która może mieć powtarzające się wartości dla określonego pola kolumny niebędącej kluczem. Jak napisać zapytanie SQL za pomocą narzędzia do tworzenia zapytań lub elokwentu, które pobierze wiersze z różnymi wartościami dla tej kolumny?

Zauważ, że nie pobieram tylko tej kolumny, jest ona połączona z innymi wartościami kolumny, więc distinct()może nie działać. Więc to pytanie może zasadniczo dotyczyć tego, jak określić kolumnę, która ma być teraz odrębna w zapytaniu, które distinct()nie akceptuje żadnych parametrów?

gthuo
źródło

Odpowiedzi:

121

Powinieneś użyć groupby. W Query Builder możesz to zrobić w ten sposób:

$users = DB::table('users')
            ->select('id','name', 'email')
            ->groupBy('name')
            ->get();
Marcin Nabiałek
źródło
2
Mam tabelę „wiadomości” (używane do czatu) i chcę otrzymywać najnowszą wiadomość, którą uwierzytelniony użytkownik otrzymał z każdej rozmowy. Korzystając z groupBy, mogę otrzymać tylko jedną wiadomość, ale otrzymuję pierwszą i potrzebuję ostatniej wiadomości. Wygląda na to, że orderBy nie działa.
JCarlosR
10
jeśli nie chcesz stosować żadnych operacji matematycznych, takich jak SUMA, ŚREDNIA, MAX itd., „grupuj według” nie jest poprawną odpowiedzią (możesz jej użyć, ale nie powinieneś jej używać). Powinieneś użyć odrębnego. W MySQL możesz użyć DISTINCT dla kolumny i dodać więcej kolumn do zestawu wyników: "SELECT DISTINCT nazwa, id, email FROM użytkowników". Z elokwentem możesz to zrobić za pomocą $ this-> select (['name', 'id', 'email']) -> another () -> get ();
Sergi,
2
kiedy dodam, where()że się psuje, jak mogę to naprawić?
tq
1
kiedy używam tego, pokazuje 'id' i 'email' nie jest w groupBy (), muszę użyć groupBy ('id', 'name', 'email'). Co nie jest przydatne.
Haque
1
Coś do zapamiętania: group byto nie to samo co distinct. group byzmieni zachowanie funkcji agregujących, takich jak count()wewnątrz zapytania SQL. distinctnie zmieni zachowania takich funkcji, więc uzyskasz różne wyniki w zależności od tego, której z nich używasz.
Skeets
80

W Eloquent możesz również zapytać w ten sposób:

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

Pathros
źródło
12
Pytanie konkretnie zadajeNote that I am not fetching that column only, it is in conjunction with other column values
Hirnhamster
12
@Hirnhamster: Okkei. Jednak ta odpowiedź jest nadal przydatna dla innych przechodzących obok ...
Pathros
3
Nie jest to dla mnie przydatne, konkretnie patrzę, czym jest OP. Nie jest to łatwe znalezisko.
pochlebca
1
$users = User::select('column1', 'column2', 'column3')->distinct()->get();
Mark-Hero
Potrzebujesz również, ->orderBy('name')jeśli chcesz używać tego z chunk.
Chibueze Opata
27

w wymowny sposób możesz tego użyć

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

groupBy w rzeczywistości pobiera różne wartości, w rzeczywistości groupBy kategoryzuje te same wartości, dzięki czemu możemy używać na nich funkcji agregujących. ale w tym scenariuszu nie mamy funkcji agregujących, po prostu wybieramy wartość, która spowoduje, że wynik będzie miał różne wartości

Salar
źródło
4
Jak to działa? Czy groupBy faktycznie pobiera różne nazwy użytkowników? Czy mógłbyś wyjaśnić trochę więcej, jak to rozwiązuje problem operacji?
Félix Gagnon-Grenier
Tak, groupBy faktycznie pobiera różne wartości, w rzeczywistości groupBy kategoryzuje te same wartości, abyśmy mogli używać na nich funkcji agregujących. ale w tym scenariuszu nie mamy funkcji agregujących, po prostu wybieramy wartość, która spowoduje, że wynik będzie miał różne wartości.
Salar
Rozumiem… w takim razie, czym różni się ta odpowiedź od Marcina? Posiadanie innej odpowiedzi jest w porządku, o ile potrafisz wyjaśnić, czym jest ona inna i jaką wadę rozwiązuje :) Nie kłopocz się odpowiadaniem w komentarzach, edytuj bezpośrednio swoje pytanie odpowiednimi informacjami.
Félix Gagnon-Grenier
1
wynik jest taki sam, zależy to od twojego projektu, aby użyć ORM lub użyć konstruktora zapytań, jeśli używasz jednego z nich, lepiej trzymać się tego. dlatego odpowiedziałem na twoje pytanie w inny sposób.
Salar
Cóż, mam ten problem, w którym, jeśli chcesz teraz sprawdzić, czy element istnieje, używając in_array()funkcji, nigdy nie działa. Aby to naprawić, spróbowałem ->lists()zamiast tego (wersja 5.2). Więc $users = User::select('name')->groupBy('name')->lists('name');działało dobrze na php in_array();
Pathros
20

Chociaż spóźniłem się na odpowiedź, lepszym podejściem do uzyskania odrębnych rekordów za pomocą Eloquent byłoby

$user_names = User::distinct()->get(['name']);
rsakhale
źródło
Ok, dodałeś wartość, pokazując nam, że można również przekazać parametry nazw pól do get()metody (a testowałem również do first()metody), co jest równoważne użyciu select()metody, jak pokazano w kilku odpowiedziach tutaj. Choć jakoś groupBynadal wydaje się mieć najwyższy wynik. Jednak byłoby to naprawdę wyraźne, gdyby była to jedyna kolumna, którą wybieram.
gthuo
Wydajność mądry, odrębny zamierza zdobyć ponad GroupBy, ponieważ zapisy zostaną wybrane w pierwszym w SQL silnika w przeciwieństwie do grupy przez silnik pierwszych wybiera wszystkie rekordy, a następnie Grupy o ..
rsakhale
1
$user_names = User::distinct()->count(['name']);działa również
Bira
12

Grupowanie według nie będzie działać, jeśli reguły bazy danych nie zezwalają na to, aby żadne z wybranych pól znajdowało się poza funkcją agregującą. Zamiast tego użyj kolekcji laravel .

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

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

Ktoś zwrócił uwagę, że w przypadku dużych kolekcji może to nie być świetne. Poleciłbym dodanie klucza do kolekcji. Metoda, której należy użyć, nazywa się keyBy . To jest prosta metoda.

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

KeyBy umożliwia również dodanie funkcji oddzwaniania dla bardziej złożonych rzeczy ...

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

Jeśli musisz iterować po dużych kolekcjach, dodanie do nich klucza rozwiązuje problem z wydajnością.

Jed Lynch
źródło
Masz rację i jest to dokładnie problem, z którym mam do czynienia ... jednak oczekiwanie na wykonanie zapytania i wykonanie akcji w kolekcji również nie jest idealne. W szczególności, gdybyś polegał na paginacji, twoja operacja byłaby po obliczeniu paginacji i elementy na stronie byłyby nieprawidłowe. Ponadto baza danych jest lepiej przystosowana do działania na dużych fragmentach danych, a nie do pobierania i przetwarzania ich w kodzie. W przypadku małych fragmentów danych byłby to mniejszy problem
ChronoFish
Masz rację. Ta metoda może nie zapewniać doskonałej wydajności w przypadku dużych zbiorów i nie będzie działać w przypadku paginacji. Myślę, że jest lepsza odpowiedź niż to, co mam.
Jed Lynch
7

**

Przetestowano dla Laravel 5.8

**

Ponieważ chcesz pobrać wszystkie kolumny z tabeli, możesz zebrać wszystkie dane, a następnie przefiltrować je za pomocą funkcji Kolekcje o nazwie Unique

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

lub

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

Więcej informacji można znaleźć w dokumentacji kolekcji Laravel

EDYCJA: Możesz mieć problem z wydajnością, używając Unique () najpierw pobierzesz wszystkie dane z tabeli User, a następnie Laravel je przefiltruje. Ten sposób nie jest dobry, jeśli masz dużo danych użytkowników. Możesz użyć konstruktora zapytań i wywołać każde pole, którego chcesz użyć, na przykład:

User::select('username','email','name')->distinct('name')->get();
Robby Alvian Jaya Mulia
źródło
Nie jest to wydajne, ponieważ najpierw otrzymujesz wszystkie dane z db
Razor Mureithi
Dlatego nazywa się to filtrowaniem, możesz użyć odrębnej () do tworzenia zapytań, ale nie możesz uzyskać innych pól (w każdym razie nadal możesz wywołać każde z pól przez select). Użycie unique () jest jedynym sposobem na uzyskanie wszystkich pól bez wywoływania każdego z nich (chociaż wiemy, że może to mieć problem z wydajnością).
Robby Alvian Jaya Mulia
6

Zwróć na to uwagę groupBy jak użyte powyżej nie będzie działać dla postgres.

Korzystanie distinctjest prawdopodobnie lepszą opcją - np $users = User::query()->distinct()->get();

Jeśli używasz query, możesz wybrać wszystkie kolumny zgodnie z żądaniem.

jec006
źródło
5

$users = User::select('column1', 'column2', 'column3')->distinct()->get();pobiera wszystkie trzy coulmns dla różnych wierszy w tabeli. Możesz dodać dowolną liczbę kolumn.

Mark-Hero
źródło
3

Zauważyłem, że ta metoda działa całkiem dobrze (dla mnie), tworząc płaską tablicę unikalnych wartości:

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

Jeśli uruchomiłeś ->toSql()ten konstruktor zapytań, zobaczysz, że generuje on takie zapytanie:

select distinct `name` from `users`

->pluck()Jest obsługiwane przez oświetlania \ lib zbiorczej (nie poprzez zapytania SQL).

Tokarski
źródło
1

Miałem te same problemy, gdy próbowałem wypełnić listę wszystkich unikalnych wątków, które użytkownik miał z innymi użytkownikami. To załatwiło sprawę dla mnie

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
źródło
0
$users = Users::all()->unique('name');
Roland Verner
źródło
5
Proszę wyjaśnić, JAK twój fragment kodu rozwiązuje problem OP.
ifconfig
0

Dla tych, którzy lubią mnie, robiąc ten sam błąd. Oto szczegółowa odpowiedź Przetestowana w Laravel 5.7

A. Zapisy w 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. Pożądany wynik zgrupowany

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

Więc przekaż tylko te kolumny "select()", których wartości są takie same. Na przykład: 'type','url'. Możesz dodać więcej kolumn, pod warunkiem że mają taką samą wartość jak 'updated_at'.

Jeśli próbujesz przekazać "created_at"lub "id"w "select()", wtedy dostaniesz zapisy samo jak A. Ponieważ są one różne dla każdego wiersza w DB.

Santosh Kumar
źródło
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
źródło
-1
$users = User::all();
foreach($users->unique('column_name') as $user){
    code here..
}
Mrunali Khandekar
źródło