Docs›Banco de dados›Operações em massa
Banco de dados

Operações em massa

update/delete em lote, agregações, insert/upsert, chunk/lazy.

Não existe mais uma classe "Repository" separada do model — toda operação em massa (update/delete em lote, agregações, inserts em bloco) é feita direto no Eloquent Query Builder, encadeado a partir da classe do model. Esta página cobre o que antes vivia numa camada de repository dedicada.

update() — atualizar em massa

// Atualiza TODOS os registros que casam o filtro, numa única query
Produto::where('categoria_id', 5)->update(['ativo' => false]);

Produto::where('categoria_id', 5)->update([
    'ativo'      => false,
    'updated_at' => now(),
]);

Update em massa não dispara eventos do model (updating/updated) — vai direto pro SQL sem instanciar nenhum model. Se você precisa dos eventos, itere e chame save() em cada um. Ver Callbacks.

delete() — excluir em massa

Produto::where('ativo', false)
    ->where('created_at', '<', '2024-01-01')
    ->delete();

Em model com HasMadSoftDeletes, isso ainda passa pelo global scope normal (só afeta os não-deletados) mas não vira UPDATE soft-delete — é DELETE físico, porque mass delete não passa pela instância onde o trait intercepta performDeleteOnModel(). Ver Soft delete.

Restaurar em massa

restore() é por instância — pra restaurar em lote, faça o update direto na coluna:

Produto::onlyTrashed()
    ->where('categoria_id', 5)
    ->update(['deleted_at' => null]);

Use o nome real da coluna: HasMadSoftDeletes permite remapear ($madAudit['deleted_at'] ou $deletedAtColumn), então num model remapeado deleted_at não existe. Em código genérico pergunte ao model: (new Produto)->getDeletedAtColumn(). E lembre que o update em massa também não limpa deleted_by/deleted_by_user_id — inclua no array se o model auditar a deleção.

Agregações

// Simples
$total = Produto::where('ativo', true)->count();
$soma  = Produto::where('ativo', true)->sum('valor');
$media = Produto::where('ativo', true)->avg('valor');
$max   = Produto::where('ativo', true)->max('valor');
$min   = Produto::where('ativo', true)->min('valor');

// Com group by — Collection de objetos stdClass
$porCategoria = Produto::where('ativo', true)
    ->selectRaw('categoria_id, COUNT(*) as total')
    ->groupBy('categoria_id')
    ->get();
// [ {categoria_id, total}, ... ]

// Múltiplas agregações na mesma query
$resultado = Produto::where('ativo', true)
    ->selectRaw('categoria_id, SUM(valor) as total_valor, COUNT(*) as total_itens')
    ->groupBy('categoria_id')
    ->get();

// Count distinct
$distintos = Produto::where('ativo', true)->distinct('categoria_id')->count('categoria_id');

pluck() — arrays pra combo/select

// [id => nome, ...]
$options = Produto::pluck('nome', 'id');

// Com filtro
$options = Produto::where('ativo', true)->orderBy('nome')->pluck('nome', 'id');

Inserts em bloco

// insert() — INSERT em lote, sem disparar eventos nem timestamps automáticos
Produto::insert([
    ['nome' => 'Camiseta', 'valor' => 59.90, 'categoria_id' => 1],
    ['nome' => 'Calça',    'valor' => 129.90, 'categoria_id' => 1],
]);

// upsert() — INSERT ou UPDATE conforme a(s) coluna(s) única(s) já existir(em)
Produto::upsert(
    [
        ['cod_barras' => '123', 'nome' => 'Camiseta', 'valor' => 59.90],
        ['cod_barras' => '456', 'nome' => 'Calça',    'valor' => 129.90],
    ],
    uniqueBy: ['cod_barras'],
    update: ['nome', 'valor']
);

Processando volumes grandes — chunk() / lazy()

Carregar uma tabela inteira com get() consome memória proporcional ao total de linhas. Pra processar em lote sem isso, use chunk()/chunkById() (recomendado quando você atualiza/deleta linhas durante o processamento — evita pular registros) ou lazy() (cursor, sem carregar tudo de uma vez):

// chunkById — seguro mesmo deletando/atualizando dentro do callback
Produto::where('ativo', true)->chunkById(200, function ($produtos) {
    foreach ($produtos as $produto) {
        $produto->recalculaEstoque();
        $produto->save();
    }
});

// lazy() — itera com cursor, sem materializar tudo em memória
foreach (Produto::where('ativo', true)->lazy() as $produto) {
    // ...
}

API — resumo

MétodoDescrição
update(array $values)UPDATE em massa — não dispara eventos
delete()DELETE em massa — físico mesmo com soft delete ativo
insert(array $rows)INSERT em lote — não dispara eventos nem timestamps
upsert($rows, $uniqueBy, $update)INSERT ou UPDATE conforme chave única
pluck($value, $key?)Mapa simples pra combos
chunk($n, $cb) / chunkById($n, $cb)Processa em lotes de tamanho fixo
lazy($chunkSize = 1000)Cursor lazy sobre todo o resultado
sum/avg/min/max/count($col)Agregações de uma coluna

Quando usar o quê

CenárioAbordagem
Buscar 1 por IDProduto::find($id)
Buscar 1 por campoProduto::where('campo', $v)->first()
Listar com filtrosProduto::where(...)->orderBy(...)->get()
Agregação com group byselectRaw(...)->groupBy(...)->get()
Update/Delete em massa (sem precisar de eventos)Model::where(...)->update() / ->delete()
Update/Delete linha a linha (precisa de eventos)Iterar + $model->save()/delete()
Combos (id => label)Produto::pluck('nome', 'id')
Processar tabela inteira sem esgotar memóriachunkById() / lazy()

Próximos