#mysql #laravel #laravel-5.7
#mysql #laravel #laravel-5.7
Вопрос:
Я работаю над маркетинговым приложением, которое позволяет пользователям отправлять сообщения своим контактам. При отправке сообщения создается новая запись базы данных «processed_message». Существует представление списка, в котором отображаются все кампании и количество отправленных, заблокированных и неудачных сообщений для каждой кампании. Моя проблема в том, что это представление списка загружается слишком долго после того, как есть > 50 кампаний с большим количеством сообщений.
В настоящее время каждая кампания имеет 3 вычисляемых атрибута (messages_sent, messages_failed и messages_blocked), которые находятся в массиве «добавления» модели кампании. Каждый атрибут запрашивает количество обработанных сообщений заданного типа для данной кампании.
namespace App;
class Campaign
{
protected $appends = [
'messages_sent',
'messages_blocked',
'messages_failed'
];
/**
* @relationship
*/
public function processed_messages()
{
return $this->hasMany(ProcessedMessage::class);
}
public function getMessagesSentAttribute()
{
return $this->processed_messages()->where('status', 'sent')->count();
}
public function getMessagesFailedAttribute()
{
return $this->processed_messages()->where('status', 'failed')->count();
}
public function getMessagesBlockedAttribute()
{
return $this->processed_messages()->where('status', 'blocked')->count();
}
}
Я также пытался запрашивать все сообщения сразу или по частям, чтобы уменьшить количество запросов, но одновременное получение всех processed_messages для кампании приведет к переполнению памяти, а метод разделения слишком медленный, поскольку он должен использовать смещение. Я рассматривал возможность использования быстрой загрузки кампаний с помощью processed_messages, но это, очевидно, также потребовало бы слишком много памяти.
namespace AppHttpControllers;
class CampaignController extends Controller
{
public function index()
{
$start = now();
$campaigns = Campaign::where('user_id', Auth::user()->id)->orderBy('updated_at', 'desc')->get();
$ids = $campaigns->map(function($camp) {
return $camp->id;
});
$statistics = ProcessedMessage::whereIn('campaign_id', $ids)->select(['campaign_id', 'status'])->get();
foreach($statistics->groupBy('campaign_id') as $group) {
foreach($group->groupBy('status') as $messages) {
$status = $messages->first()->status;
$attr = "messages_$status";
$campaign = $campaigns->firstWhere('id', $messages->first()->campaign_id);
$campaign->getStatistics()->$attr = $status;
}
}
return view('campaign.index', [
'campaigns' => $campaigns
]);
}
}
Моя главная цель — значительно сократить текущее время загрузки страницы (которое может занимать от 30 секунд до 5 минут при наличии множества кампаний).
Ответ №1:
Вы могли бы использовать withCount
метод для подсчета всех объектов без загрузки отношения.
Ссылка:
Если вы хотите подсчитать количество результатов из отношения, фактически не загружая их, вы можете использовать метод withCount, который поместит столбец {relation}_count в ваши результирующие модели.
В вашем контроллере вы могли бы сделать это:
$count = Campaign::withCount(['processed_messages' => function ($query) {
$query->where('content', 'sent');
}])->get();
Вы также можете выполнять несколько подсчетов в одном и том же отношении:
$campaigns = Campaign::withCount([
'processed_messages',
'processed_messages as sent_message_count' => function ($query) {
$query->where('content', 'sent');
}],
'processed_messages as failed_message_count' => function ($query) {
$query->where('status', 'failed');
}],
'processed_messages as blocked_message_count' => function ($query) {
$query->where('status', 'blocked');
}])->get();
Вы можете получить доступ к подсчету с помощью этого:
echo $campaigns[0]->sent_message_count
Комментарии:
1. Как бы я использовал 2-й метод, если бы хотел загрузить все 3 отношения?
2. Вы могли бы передать массив в качестве параметра этого метода
3. Но какие ключи результирующая модель будет использовать для атрибута? Если я выполню подсчет 3 раза, не будет ли это просто сохранять все результаты в столбце с именем «processed_messages_count», и будет применяться только последний? Есть ли способ настроить имя атрибута count ({отношение}_count)?
4. да, вы можете настроить этот атрибут, я отредактировал свой ответ, взгляните
5. Спасибо за разъяснение. Первый имеющийся у вас блок кода на самом деле не работает (метод withCount не существует), но второй значительно ускорил мои тесты, и в моем случае мне даже не нужно загружать отношение «processed_messages».