From 8b09cfb8c97ee5d25dfc7f1dc39df51bef1c6b74 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 26 Sep 2025 19:32:53 +0200 Subject: [PATCH] Optimize query for period statistics. --- .../Account/AccountRepository.php | 1 + .../PeriodStatisticRepository.php | 27 ++++++++------- .../Http/Controllers/PeriodOverview.php | 34 +++++++++++++++---- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index c2a14d8d81..caf928b771 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -546,6 +546,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac #[Override] public function periodCollection(Account $account, Carbon $start, Carbon $end): array { + Log::debug(sprintf('periodCollection(#%d, %s, %s)', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d'))); return $account->transactions() ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') diff --git a/app/Repositories/PeriodStatistic/PeriodStatisticRepository.php b/app/Repositories/PeriodStatistic/PeriodStatisticRepository.php index 1762329f12..00da6827ee 100644 --- a/app/Repositories/PeriodStatistic/PeriodStatisticRepository.php +++ b/app/Repositories/PeriodStatistic/PeriodStatisticRepository.php @@ -27,32 +27,31 @@ use Carbon\Carbon; use FireflyIII\Models\PeriodStatistic; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface { public function findPeriodStatistics(Model $model, Carbon $start, Carbon $end, array $types): Collection { return $model->primaryPeriodStatistics() - ->where('start', $start) - ->where('end', $end) - ->whereIn('type', $types) - ->get() - ; + ->where('start', $start) + ->where('end', $end) + ->whereIn('type', $types) + ->get(); } public function findPeriodStatistic(Model $model, Carbon $start, Carbon $end, string $type): Collection { return $model->primaryPeriodStatistics() - ->where('start', $start) - ->where('end', $end) - ->where('type', $type) - ->get() - ; + ->where('start', $start) + ->where('end', $end) + ->where('type', $type) + ->get(); } public function saveStatistic(Model $model, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic { - $stat = new PeriodStatistic(); + $stat = new PeriodStatistic(); $stat->primaryStatable()->associate($model); $stat->transaction_currency_id = $currencyId; $stat->start = $start; @@ -64,11 +63,15 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface $stat->type = $type; $stat->save(); + Log::debug(sprintf('Saved #%d [currency #%d, Model %s #%d, %s to %s, %d, %s] as new statistic.', + $stat->id, get_class($model), $model->id, $stat->transaction_currency_id, $stat->start->toW3cString(), $stat->end->toW3cString(), $count, $amount + )); + return $stat; } public function allInRangeForModel(Model $model, Carbon $start, Carbon $end): Collection { - return $model->primaryPeriodStatistics()->where('start','>=', $start)->where('end','<=', $end)->get(); + return $model->primaryPeriodStatistics()->where('start', '>=', $start)->where('end', '<=', $end)->get(); } } diff --git a/app/Support/Http/Controllers/PeriodOverview.php b/app/Support/Http/Controllers/PeriodOverview.php index 1914b7ce0b..9012e471b9 100644 --- a/app/Support/Http/Controllers/PeriodOverview.php +++ b/app/Support/Http/Controllers/PeriodOverview.php @@ -90,6 +90,9 @@ trait PeriodOverview $range = Navigation::getViewRange(true); [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; + /** @var array $dates */ + $dates = Navigation::blockPeriods($start, $end, $range); + [$start, $end] = $this->getPeriodFromBlocks($dates, $start, $end); $this->statistics = $this->periodStatisticRepo->allInRangeForModel($account, $start, $end); // TODO needs to be re-arranged: @@ -97,10 +100,8 @@ trait PeriodOverview // loop blocks, an loop the types, and select the missing ones. // create new ones, or use collected. - /** @var array $dates */ - $dates = Navigation::blockPeriods($start, $end, $range); + $entries = []; - $types = ['spent', 'earned', 'transferred_in', 'transferred_away']; Log::debug(sprintf('Count of loops: %d', count($dates))); foreach ($dates as $currentDate) { $entries[] = $this->getSingleAccountPeriod($account, $currentDate['period'], $currentDate['start'], $currentDate['end']); @@ -110,6 +111,25 @@ trait PeriodOverview return $entries; } + private function getPeriodFromBlocks(array $dates, Carbon $start, Carbon $end): array + { + Log::debug('Filter generated periods to select the oldest and newest date.'); + foreach ($dates as $row) { + $currentStart = clone $row['start']; + $currentEnd = clone $row['end']; + if ($currentStart->lt($start)) { + Log::debug(sprintf('New start: was %s, now %s', $start->format('Y-m-d'), $currentStart->format('Y-m-d'))); + $start = $currentStart; + } + if ($currentEnd->gt($end)) { + Log::debug(sprintf('New end: was %s, now %s', $end->format('Y-m-d'), $currentEnd->format('Y-m-d'))); + $end = $currentEnd; + } + } + + return [$start, $end]; + } + /** * Overview for single category. Has been refactored recently. * @@ -326,7 +346,7 @@ trait PeriodOverview { return $this->statistics->filter( function (PeriodStatistic $statistic) use ($start, $end, $type) { - if( + if ( !$statistic->end->equalTo($end) && $statistic->end->format('Y-m-d H:i:s') === $end->format('Y-m-d H:i:s') ) { @@ -377,9 +397,9 @@ trait PeriodOverview break; } // each result must be grouped by currency, then saved as period statistic. + Log::debug(sprintf('Going to group %d found journal(s)', count($result))); $grouped = $this->groupByCurrency($result); - // TODO save as statistic. $this->saveGroupedAsStatistics($account, $start, $end, $type, $grouped); return $grouped; @@ -547,10 +567,12 @@ trait PeriodOverview protected function saveGroupedAsStatistics(Account $account, Carbon $start, Carbon $end, string $type, array $array): void { unset($array['count']); + Log::debug(sprintf('saveGroupedAsStatistics(#%d, %s, %s, "%s", array(%d))', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d'), $type, count($array))); foreach ($array as $entry) { $this->periodStatisticRepo->saveStatistic($account, $entry['currency_id'], $start, $end, $type, $entry['count'], $entry['amount']); } - if(0 === count($array)) { + if (0 === count($array)) { + Log::debug('Save empty statistic.'); $this->periodStatisticRepo->saveStatistic($account, $this->primaryCurrency->id, $start, $end, $type, 0, '0'); } }