Как получить отдельные значения для полей неключевых столбцов в Laravel?


94

Это может быть довольно просто, но я не знаю, как это сделать.

У меня есть таблица, в которой могут быть повторяющиеся значения для определенного поля неключевого столбца. Как написать SQL-запрос с помощью Query Builder или Eloquent, который будет извлекать строки с различными значениями для этого столбца?

Обратите внимание, что я беру не только этот столбец, он связан с другими значениями столбца, поэтому distinct()может не работать. Итак, этот вопрос в основном может заключаться в том, как указать столбец, который я хочу выделить в запросе, который теперь distinct()не принимает никаких параметров?

Ответы:


116

Вам следует использовать groupby. В Query Builder это можно сделать так:

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

2
У меня есть таблица сообщений (используется для чата), и я хочу получить последнее сообщение, которое аутентифицированный пользователь получил от каждого разговора. Используя groupBy, я могу получить только одно сообщение, но получаю первое и мне нужно последнее сообщение. Похоже, что orderBy не работает.
JCarlosR

10
если вы не хотите применять какие-либо математические операции, такие как SUM, AVG, MAX и т. д., «группировать по» - неправильный ответ (вы можете его использовать, но не должны). Вы должны использовать отличный. В MySQL вы можете использовать DISTINCT для столбца и добавить больше столбцов к набору результатов: «ВЫБРАТЬ DISTINCT имя, идентификатор, электронная почта ОТ пользователей». С помощью Eloquent вы можете сделать это с помощью $ this-> select (['name', 'id', 'email']) -> independent () -> get ();
Sergi

1
когда я добавляю, что where()он ломается, как я могу это исправить?
tq

1
когда я использую это, он показывает 'id', а 'email' не находится в groupBy (), мне нужно использовать groupBy ('id', 'name', 'email'). Что бесполезно.
Haque

1
Обратите внимание: group byэто не то же самое, что distinct. group byизменит поведение агрегатных функций, как count()внутри запроса SQL. distinctне изменит поведение таких функций, поэтому вы получите разные результаты в зависимости от того, какую из них вы используете.
Скитс

78

В Eloquent вы также можете запросить следующее:

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


12
Вопрос конкретно задаетNote that I am not fetching that column only, it is in conjunction with other column values
Hirnhamster

12
@Hirnhamster: Оккей. Однако этот ответ по-прежнему полезен для других, проходящих мимо ...
Патрос

3
Для меня это бесполезно, я специально смотрю, что такое OP. Это непростая находка.
blamb

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

Также вам нужно, ->orderBy('name')если вы хотите использовать это с chunk.
Chibueze Opata,

26

красноречиво вы можете использовать это

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

groupBy фактически выбирает отдельные значения, на самом деле groupBy классифицирует одни и те же значения, чтобы мы могли использовать для них агрегатные функции. но в этом сценарии у нас нет агрегатных функций, мы просто выбираем значение, которое приведет к тому, что результат будет иметь разные значения


4
Как это работает? Действительно ли groupBy получает отдельные имена пользователей? Не могли бы вы объяснить еще немного, как это решает проблему op?
Феликс Ганьон-Гренье,

Да, groupBy на самом деле получает отдельные значения, на самом деле groupBy классифицирует одни и те же значения, чтобы мы могли использовать для них агрегатные функции. но в этом сценарии у нас нет агрегатных функций, мы просто выбираем значение, которое приведет к тому, что результат будет иметь разные значения.
Салар

Понятно ... тогда чем этот ответ отличается от ответа Марцина? Иметь другой ответ - это нормально, если вы можете объяснить, чем он отличается и какой недостаток он решает :) Не трудитесь отвечать в комментариях, пожалуйста, напрямую отредактируйте свой вопрос, указав соответствующую информацию.
Феликс Ганьон-Гренье

1
результат такой же, это зависит от вашего проекта, чтобы использовать ORM или использовать построитель запросов, если вы используете один из них, то лучше придерживаться этого. поэтому я ответил на ваш вопрос иначе.
Салар

Что ж, у меня есть проблема, когда, если вы теперь хотите проверить, существует ли элемент с помощью in_array()функции, он никогда не работает. Чтобы исправить это, я попробовал ->lists()(Версия 5.2). Итак, $users = User::select('name')->groupBy('name')->lists('name');отлично работает для php in_array();
Pathros

19

Хотя я опоздал с ответом на этот вопрос, лучшим подходом к получению отдельных записей с помощью Eloquent было бы

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

Хорошо, вы добавили ценность, показав нам, что можно также передавать параметры имен полей get()методу (и я также тестировал этот first()метод), что эквивалентно использованию select()метода, как показано в нескольких ответах здесь. Хотя почему-то groupByвсе равно набирает наибольшее количество баллов. Однако это было бы по-настоящему отличным, если бы я выбирал только этот столбец.
gthuo 07

С точки зрения производительности, отличная будет лучше groupBy, потому что записи будут сначала выбираться в SQL Engine, в отличие от Group By, сначала движок выбирает все записи, а затем группирует по ..
rsakhale

1
$user_names = User::distinct()->count(['name']);тоже работает
Бира

11

Группировка по не будет работать, если правила базы данных не позволяют ни одному из выбранных полей находиться за пределами агрегатной функции. Вместо этого используйте коллекции laravel .

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

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

Кто-то заметил, что это может не сильно сказаться на производительности для больших коллекций. Я бы порекомендовал добавить ключ в коллекцию. Используемый метод называется keyBy . Это простой метод.

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

KeyBy также позволяет добавлять функцию обратного вызова для более сложных вещей ...

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

Если вам нужно перебирать большие коллекции, добавление ключа решает проблему с производительностью.


Вы правы, и это именно та проблема, с которой я столкнулся ... однако ожидание выполнения запроса и выполнения действия в коллекции также не идеально. В частности, если вы полагались на разбиение на страницы, ваша операция будет после расчета разбивки на страницы, и элементы на странице будут неправильными. Кроме того, БД лучше подходит для работы с большими фрагментами данных, а не для их извлечения и обработки в коде. Для небольших фрагментов данных это не будет проблемой
ChronoFish

Вы подметили. Этот метод может не иметь большой производительности для больших коллекций, и он не будет работать для разбивки на страницы. Я думаю, что есть лучший ответ, чем то, что у меня есть.
Джед Линч,

6

Обратите внимание, что groupByиспользованное выше не будет работать для postgres.

Использование distinct, вероятно, лучший вариант - например, $users = User::query()->distinct()->get();

Если вы используете, queryвы можете выбрать все столбцы по запросу.


6

**

Протестировано для Laravel 5.8

**

Поскольку вы хотите получить все столбцы из таблицы, вы можете собрать все данные, а затем отфильтровать их с помощью функции Collections, называемой Unique.

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

или

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

Для получения дополнительной информации вы можете проверить Документацию коллекции Laravel.

EDIT: у вас могут быть проблемы с производительностью, используя Unique (), вы сначала получите все данные из таблицы User, а затем Laravel отфильтрует их. Этот способ не годится, если у вас много данных о пользователях. Вы можете использовать построитель запросов и вызывать каждое поле, которое хотите использовать, например:

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

Это неэффективно, поскольку вы сначала получаете все данные из
базы

Вот почему это называется фильтрацией, вы можете использовать отдельный () для построителя запросов, но вы не можете получить другие поля (в любом случае вы все равно можете вызывать каждое из полей через select). Использование unique () - единственный способ получить все поля без вызова каждого из них (хотя мы знаем, что это может иметь проблемы с производительностью).
Робби Алвиан Джая Мулиа

5

$users = User::select('column1', 'column2', 'column3')->distinct()->get();извлекает все три кулона для отдельных строк в таблице. Вы можете добавить столько столбцов, сколько захотите.


3

Я обнаружил, что этот метод работает достаточно хорошо (для меня) для создания плоского массива уникальных значений:

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

Если вы запустили ->toSql()этот конструктор запросов, вы увидите, что он генерирует такой запрос:

select distinct `name` from `users`

Это ->pluck()обрабатывается светом \ collection lib (не через sql-запрос).


1

У меня были те же проблемы при попытке заполнить список всех уникальных потоков, которые пользователь имел с другими пользователями. Это помогло мне

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

Для тех, кто вроде меня совершает ту же ошибку. Вот подробный ответ. Протестировано в Laravel 5.7.

A. Записи в БД

UserFile :: orderBy ('created_at', 'desc') -> получить () -> 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
        )
)

Б. Желаемый сгруппированный результат

UserFile :: select ('тип', 'url', 'updated_at) -> отдельный (' тип ') -> 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
        )
)

Поэтому передавайте только те столбцы "select()", значения которых одинаковы. Например: 'type','url'. Вы можете добавить больше столбцов, если они имеют такое же значение, как 'updated_at'.

Если вы пытаетесь пройти "created_at"или "id"в "select()", то вы получите записи такой же , как А. Так как они различны для каждой строки в БД.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.