Optimize queries for statistics.

This commit is contained in:
James Cole
2025-09-26 06:05:37 +02:00
parent 08879d31ba
commit 4ec2fcdb8a
92 changed files with 6499 additions and 6514 deletions

View File

@@ -62,6 +62,46 @@ class AccountBalanceCalculator
$object->optimizedCalculation(new Collection());
}
public static function recalculateForJournal(TransactionJournal $transactionJournal): void
{
Log::debug(__METHOD__);
$object = new self();
$set = [];
foreach ($transactionJournal->transactions as $transaction) {
$set[$transaction->account_id] = $transaction->account;
}
$accounts = new Collection()->push(...$set);
$object->optimizedCalculation($accounts, $transactionJournal->date);
}
private function getLatestBalance(int $accountId, int $currencyId, ?Carbon $notBefore): string
{
if (!$notBefore instanceof Carbon) {
return '0';
}
Log::debug(sprintf('getLatestBalance: notBefore date is "%s", calculating', $notBefore->format('Y-m-d')));
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->whereNull('transactions.deleted_at')
->where('transaction_journals.transaction_currency_id', $currencyId)
->whereNull('transaction_journals.deleted_at')
// this order is the same as GroupCollector
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC')
->orderBy('transaction_journals.description', 'DESC')
->orderBy('transactions.amount', 'DESC')
->where('transactions.account_id', $accountId);
$notBefore->startOfDay();
$query->where('transaction_journals.date', '<', $notBefore);
$first = $query->first(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount', 'transactions.balance_after']);
$balance = (string)($first->balance_after ?? '0');
Log::debug(sprintf('getLatestBalance: found balance: %s in transaction #%d', $balance, $first->id ?? 0));
return $balance;
}
private function optimizedCalculation(Collection $accounts, ?Carbon $notBefore = null): void
{
Log::debug('start of optimizedCalculation');
@@ -72,15 +112,14 @@ class AccountBalanceCalculator
$balances = [];
$count = 0;
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
// this order is the same as GroupCollector, but in the exact reverse.
->orderBy('transaction_journals.date', 'asc')
->orderBy('transaction_journals.order', 'desc')
->orderBy('transaction_journals.id', 'asc')
->orderBy('transaction_journals.description', 'asc')
->orderBy('transactions.amount', 'asc')
;
->orderBy('transaction_journals.date', 'asc')
->orderBy('transaction_journals.order', 'desc')
->orderBy('transaction_journals.id', 'asc')
->orderBy('transaction_journals.description', 'asc')
->orderBy('transactions.amount', 'asc');
if ($accounts->count() > 0) {
$query->whereIn('transactions.account_id', $accounts->pluck('id')->toArray());
}
@@ -89,7 +128,7 @@ class AccountBalanceCalculator
$query->where('transaction_journals.date', '>=', $notBefore);
}
$set = $query->get(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount']);
$set = $query->get(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount']);
Log::debug(sprintf('Counted %d transaction(s)', $set->count()));
// the balance value is an array.
@@ -102,8 +141,8 @@ class AccountBalanceCalculator
$balances[$entry->account_id][$entry->transaction_currency_id] ??= [$this->getLatestBalance($entry->account_id, $entry->transaction_currency_id, $notBefore), null];
// before and after are easy:
$before = $balances[$entry->account_id][$entry->transaction_currency_id][0];
$after = bcadd($before, (string)$entry->amount);
$before = $balances[$entry->account_id][$entry->transaction_currency_id][0];
$after = bcadd($before, (string)$entry->amount);
if (true === $entry->balance_dirty || $accounts->count() > 0) {
// update the transaction:
$entry->balance_before = $before;
@@ -123,34 +162,6 @@ class AccountBalanceCalculator
$this->storeAccountBalances($balances);
}
private function getLatestBalance(int $accountId, int $currencyId, ?Carbon $notBefore): string
{
if (!$notBefore instanceof Carbon) {
return '0';
}
Log::debug(sprintf('getLatestBalance: notBefore date is "%s", calculating', $notBefore->format('Y-m-d')));
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->whereNull('transactions.deleted_at')
->where('transaction_journals.transaction_currency_id', $currencyId)
->whereNull('transaction_journals.deleted_at')
// this order is the same as GroupCollector
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC')
->orderBy('transaction_journals.description', 'DESC')
->orderBy('transactions.amount', 'DESC')
->where('transactions.account_id', $accountId)
;
$notBefore->startOfDay();
$query->where('transaction_journals.date', '<', $notBefore);
$first = $query->first(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount', 'transactions.balance_after']);
$balance = (string)($first->balance_after ?? '0');
Log::debug(sprintf('getLatestBalance: found balance: %s in transaction #%d', $balance, $first->id ?? 0));
return $balance;
}
private function storeAccountBalances(array $balances): void
{
/**
@@ -196,17 +207,4 @@ class AccountBalanceCalculator
}
}
}
public static function recalculateForJournal(TransactionJournal $transactionJournal): void
{
Log::debug(__METHOD__);
$object = new self();
$set = [];
foreach ($transactionJournal->transactions as $transaction) {
$set[$transaction->account_id] = $transaction->account;
}
$accounts = new Collection()->push(...$set);
$object->optimizedCalculation($accounts, $transactionJournal->date);
}
}

View File

@@ -49,15 +49,15 @@ class BillDateCalculator
Log::debug(sprintf('Dates must be between %s and %s.', $earliest->format('Y-m-d'), $latest->format('Y-m-d')));
Log::debug(sprintf('Bill started on %s, period is "%s", skip is %d, last paid = "%s".', $billStart->format('Y-m-d'), $period, $skip, $lastPaid?->format('Y-m-d')));
$daysUntilEOM = app('navigation')->daysUntilEndOfMonth($billStart);
$daysUntilEOM = app('navigation')->daysUntilEndOfMonth($billStart);
Log::debug(sprintf('For bill start, days until end of month is %d', $daysUntilEOM));
$set = new Collection();
$currentStart = clone $earliest;
$set = new Collection();
$currentStart = clone $earliest;
// 2023-06-23 subDay to fix 7655
$currentStart->subDay();
$loop = 0;
$loop = 0;
Log::debug('Start of loop');
while ($currentStart <= $latest) {
@@ -107,7 +107,7 @@ class BillDateCalculator
// for the next loop, go to end of period, THEN add day.
Log::debug('Add one day to nextExpectedMatch/currentStart.');
$nextExpectedMatch->addDay();
$currentStart = clone $nextExpectedMatch;
$currentStart = clone $nextExpectedMatch;
++$loop;
if ($loop > 31) {
@@ -117,8 +117,8 @@ class BillDateCalculator
}
}
Log::debug('end of loop');
$simple = $set->map( // @phpstan-ignore-line
static fn (Carbon $date) => $date->format('Y-m-d')
$simple = $set->map( // @phpstan-ignore-line
static fn(Carbon $date) => $date->format('Y-m-d')
);
Log::debug(sprintf('Found %d pay dates', $set->count()), $simple->toArray());
@@ -140,7 +140,7 @@ class BillDateCalculator
return $billStartDate;
}
$steps = app('navigation')->diffInPeriods($period, $skip, $earliest, $billStartDate);
$steps = app('navigation')->diffInPeriods($period, $skip, $earliest, $billStartDate);
if ($steps === $this->diffInMonths) {
Log::debug(sprintf('Steps is %d, which is the same as diffInMonths (%d), so we add another 1.', $steps, $this->diffInMonths));
++$steps;

View File

@@ -39,7 +39,7 @@ trait ReturnsIntegerIdTrait
protected function id(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn($value) => (int)$value,
);
}
}

View File

@@ -37,14 +37,14 @@ trait ReturnsIntegerUserIdTrait
protected function userGroupId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn($value) => (int)$value,
);
}
protected function userId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn($value) => (int)$value,
);
}
}