Все мы знаем, что запускать в цикле запросы к базе данных – плохое решение.
Транзакция к базе данных очень дорогостоящая операция
Допустим, мы разработали систему учета финансов и пользовались ею в течении года. За это время накопилось более миллиона записей в таблице успешных сделок.
Нам поставили задачу, что необходимо для всех цен добавить НДС. А чтобы аналитика не поплыла, НДС надо добавить и для старых записей. Пример так себе, но все же.
Для новых сделок задача простая, можно воспользоваться мутатором:
class Transaction extends Model
{
public $vat = 0.20;
public function setPriceAttribute($value)
{
$this->attributes['price'] += $value * $this->vat;
}
}
Но как поступить с историческими данными. Какая первая мысль поступить?
Создаем сидер
php artisan make:seeder AddVatToTransactions
Если у нас не много записей, то мы могли бы поступить следующим образом:
class AddVatToTransactions extends Seeder
{
public function run() {
$vat = 0.20;
$transactions = Transaction::get();
foreach ($transactions as $transaction)
{
$transaction->price += $transaction->price * $vat;
$transaction->save();
}
}
}
Но если у нас будет миллион записей, то скорее всего наш сайт повиснет от огромного кол-ва запросов к бд.
Есть мысль заменить множество отдельных запросов на один, используя case:
UPDATE db.transactions
SET PRICE = CASE
WHEN id = 3 THEN 500
WHEN id = 4 THEN 300
END
WHERE ID IN (3, 4)
На laravel у нас получится что-то вроде такого:
$vat = 0.20;
$transactions = Transaction::get();
$cases = [];
$ids = [];
$params = [];
foreach ($transactions as $transaction)
{
$cases[] = "WHEN {$transaction->id} then ?";
$params[] = $transaction->profit * $vat;
$ids[] = $transaction->id;
}
$ids = implode(',', $ids);
$cases = implode(' ', $cases);
if (!empty($ids)) {
\DB::update("UPDATE transactions SET `price` = CASE `id` {$cases} END WHERE `id` in ({$ids})", $params);
}
Но система явно так же будет не в восторге от миллиона case’ов. Давайте воспользуемся коллекциями Laravel и разобьем на чанки. Т.к. нам требуются только id и profit, то добавим выборку только этих данных:
$vat = 0.20;
$transactions = Transaction::select('id', 'price')->get();
foreach ($transactions->chunk(5000) as $chunk) {
$cases = [];
$ids = [];
$params = [];
foreach ($chunk as $transaction) {
$cases[] = "WHEN {$transaction->id} then ?";
$params[] = $transaction->profit * $vat;
$ids[] = $transaction->id;
}
$ids = implode(',', $ids);
$cases = implode(' ', $cases);
if (!empty($ids)) {
\DB::update("UPDATE transactions SET `price` = CASE `id` {$cases} END WHERE `id` in ({$ids})", $params);
}
}