Как использовать пользовательский SELECT с JOIN и GROUP BY в модели Laravel? - PullRequest
2 голосов
/ 17 мая 2019

Я хочу использовать сложный запрос SELECT с JOIN и GROUP BY в модели Laravel.

Конкретно хочу сделать мессенджер в своем приложении. Вот таблица "сообщения" со всеми сообщениями. Теперь я хочу создать модель под названием «Диалог». Имейте в виду, что здесь нет таблицы «диалогов», диалог является результатом объединения и группировки.

Пример запроса:

SELECT 
    cl.name                               AS client_name,
    COUNT(m.id)                           AS messages_count, 
    MAX(m.created_at)                     AS last_message,
    COUNT(m.id) > SUM(m.viewed_by_client) AS has_new_for_client,    
    COUNT(m.id) > SUM(m.viewed_by_user)   AS has_new_for_user

FROM messages AS m
INNER JOIN clients AS c ON m.client_id = c.id
GROUP BY c.id

Конечно, я могу использовать необработанные запросы SQL. Но я хочу использовать отношения Eloquent позже со всеми его преимуществами. Например:

$dialog->client->full_name
$dialog->client->order->ordered_items

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

Итак, как я могу использовать JOINs и GROUP BY в Eloquent, если у меня нет реальной таблицы для сущностей модели? Или могут быть какие-то разные решения для моей задачи?

Ответы [ 2 ]

2 голосов
/ 18 мая 2019

Вы можете иметь таблицу базы данных без модели Eloquent, но не наоборот. Тем не менее, нет правила против создания более 1 модели на стол. Не совсем стандартная практика.

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

Я думаю, вы можете получить всю информацию, полученную из этого запроса, с помощью аксессоров в вашей Client модели. Поскольку в вашем запросе нет условия where, scope не является действительно необходимым, но это также можно сделать с помощью этого.

ВАРИАНТ 1: Средства доступа

# App\Client
class Client extends Model
{
    // Standard Eloquent relationship
    public function messages()
    {
        return $this->hasMany(App\Message::class);
    }
    // Accessor $client->client_name
    public function getClientNameAttribute()
    {
        return $this->name;
    }
    // Accessor $client->last_message
    public function getLastMessageAttribute()
    {
        // Load relationship only if it hasn't been loaded yet
        if(!$this->relationshipLoaded('messages'))
            $this->load('messages');
        // use max() method from collection to get the results
        return $this->messages->max('created_at');
    }

    // Accessor $client->has_new_for_client
    public function getHasNewForClientAttribute()
    {
        // Load relationship only if it hasn't been loaded yet
        if(!$this->relationshipLoaded('messages'))
            $this->load('messages');

        return $this->messages->count() > $this->messages->sum('viewed_by_client');
    }

    // Accessor $client->has_new_for_user
    public function getHasNewForUserAttribute()
    {
        // Load relationship only if it hasn't been loaded yet
        if(!$this->relationshipLoaded('messages'))
            $this->load('messages');

        return $this->messages->count() > $this->messages->sum('viewed_by_user');
    }
}

И тогда вы можете получить динамический доступ ко всем свойствам

$dialog = Client::withCount('messages')->find($id);
$dialog->client_name;
$dialog->messages_count;
$dialog->has_new_for_client;
$dialog->has_new_for_user;
$dialog->last_message;

Однако, если вы конвертируете $dialog в формат массива или json, средства доступа будут потеряны, если вы не добавите их . Таким же образом вы можете скрыть атрибуты, которые вы не хотите показывать.

Это можно сделать глобально для модели

protected $appends = ['client_name', 'has_new_for_client', 'has_new_for_user', 'last_message'];
protected $hidden = ['name'];

или локально для запроса

$dialog->setHidden(['name']);
$dialog->setAppends(['client_name', 'has_new_for_client', 'has_new_for_user', 'last_message'];

ВАРИАНТ 2: Области запросов

# App\Client
class Client extends Model
{
    public function scopeDialog($query)
    {
        $query->select('name as client_name')
              ->withCount('messages') // the default name will be messages_count
              ->selectRaw('max(m.created_at) as last_message')
              ->selectRaw('count(m.id) > sum(m.viewed_by_client) as has_new_for_client')
              ->selectRaw('count(m.id) > sum(m.viewed_by_user) as has_new_for_user')
              ->join('messages as m', 'm.client_id', 'clients.id')
              ->groupBy('clients.id');
    }
}

А затем просто назовите его так, как если бы вы использовали любую область действия Client :: dialog () -> ...

ВАРИАНТ 3: Просто используйте те методы, которые уже доступны, вместо того, чтобы писать больше логики

$dialog = Client::with('messages')->find($id);
// client_name
$dialog->name
// messages_count
$dialog->messages->count()
// last_message
$dialog->messages->max('created_at')
// has_new_for_client
($dialog->messages->count('id') > $dialog->messages->count('viewed_by_client'))
// has_new_for_user
($dialog->messages->count('id') > $dialog->messages->count('viewed_by_user'))
0 голосов
/ 17 мая 2019

Создайте таблицу диалогов и вставьте столбец 'dialog_id' в таблицу сообщений.Каждое сообщение имеет диалог и клиента.Создайте отношения в каждой модели.Таким образом, вы можете получить доступ к атрибутам моделей, как вы хотите.Делая это, этот код работает;

$dialog->client->full_name
$dialog->client->order->ordered_items
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...