Cómo utilizar upsert en Laravel para actualizar o crear registros en la base de datos

Spread the love

Seguro que alguna vez te has encontrado con una situación en la que necesitabas hacer algún tipo de actualización masiva o sincronización con datos externos. Una forma sencilla de hacerlo podría ser similar a la siguiente.

collect($csvContacts)->each(function (array $row) {
    $contact = Contact::updateOrCreate(
        ['email' => $row['email']],
        ['name' => $row['name'], 'address' => $row['address']]
    );
});

Por debajo, esto hará 2 consultas para cada registro. En primer lugar, buscará la primera fila que coincida con todos los pares clave/valor del primer arreglo. Si ya existe una fila, la actualizará; de lo contrario, insertará una nueva fila.

Con importaciones pequeñas esto funcionará bien, pero imagina que tienes 100.000 filas en un archivo CSV. Esto daría lugar a 200.000 consultas, que van a tardar una eternidad.

Uso de upsert

El método upsert insertará registros que no existen y actualizará los registros que ya existen con los nuevos valores que especifiques todo en una sola consulta.

Todas las bases de datos, excepto SQL Server requieren que las columnas del segundo argumento del método upsert tengan un índice “primario” o “único”. Además, el controlador de base de datos MySQL ignora el segundo argumento del método upsert y utiliza siempre los índices “primario” y “único” de la tabla para detectar los registros existentes.

Obviamente, hacer una sola consulta es mucho más eficiente y permite que la base de datos compute internamente las filas duplicadas (lo cual es muy rápido, ya que tienes un índice en la columna).

También es una buena idea dividir estas consultas en bloques, especialmente si tus consultas son de inserción/actualización de muchos datos. Si no lo haces, es posible que te encuentres con límites de tamaño de consulta en algunas bases de datos.

Actualizamos nuestro ejemplo anterior ahora usando Upsert

collect($csvContacts)
    ->map(function (array $row) {
        return Arr::only($row, ['email', 'name', 'address']);
    })
    ->chunk(1000)
    ->each(function (Collection $chunk) {
        Contact::upsert($chunk, 'email'); //👈
    });

Esto funciona y es una sola consulta por registro. En términos de rendimiento, utilizar las comparaciones sql en lugar de php ejecuta de forma muy rápida la creación o actualización de cada registro.

Gracias a todos y espero que encuentren esto útil.