Saltar al contenido
Volver al blog
LaravelPHPBuenas prácticas

Laravel 13.30.1 devuelve las filas creadas al insertar sin duplicados

`insertOrIgnoreReturning()` simplifica importadores idempotentes y evita consultas extra para saber qué filas se crearon.

Ismael Catala4 min de lectura

El problema no es insertar, sino saber qué ha entrado

En importadores, sincronizaciones y trabajos en cola es habitual recibir el mismo dato más de una vez. Una restricción única evita el duplicado, pero hasta ahora era fácil acabar haciendo una inserción y después una consulta adicional para descubrir qué registros se habían creado. Esa segunda consulta complica el flujo y puede obligarnos a mantener más estado del necesario.

Laravel 13.30.1 incorpora un ajuste alrededor de insertOrIgnoreReturning(). El método pertenece al Query Builder, acepta los valores a insertar, las columnas que queremos recuperar y un criterio opcional de conflicto mediante uniqueBy. El resultado es una colección con las filas devueltas por la inserción, no un simple contador de filas afectadas. (api.laravel.com)

Una operación útil para cargas repetibles

La idea encaja bien cuando cada registro externo tiene una clave estable, como un ID de proveedor, un UUID remoto o el identificador de un documento. Si el proceso se reintenta, las filas que ya existen no deberían duplicarse. Y las que sí se insertan pueden pasar directamente al siguiente paso: indexado, creación de relaciones o envío de otro trabajo.

Este ejemplo guarda elementos recibidos desde una API externa y recupera solamente el identificador local y la clave externa. La columna external_id debe estar respaldada por una restricción única en la base de datos para que el conflicto tenga una definición real. El valor de retorno permite trabajar únicamente con los elementos creados en esta ejecución.

use Illuminate\Support\Facades\DB;
 
$created = DB::table('external_items')->insertOrIgnoreReturning(
    [
        ['external_id' => 'api-1001', 'name' => 'Router'],
        ['external_id' => 'api-1002', 'name' => 'Switch'],
    ],
    ['id', 'external_id'],
    ['external_id'],
);
 
foreach ($created as $item) {
    SyncExternalItemMetadata::dispatch($item->id);
}

También desde un builder de Eloquent

La corrección incluida en esta versión evita perder el valor devuelto al llamar al método desde un builder de Eloquent. Eso importa si el código ya parte de un modelo y no de DB::table(). El objetivo no es convertir la respuesta en instancias completas del modelo, sino conservar la colección producida por la consulta. (github.com)

Yo lo usaría en procesos donde el resultado de la inserción decide el trabajo posterior. Por ejemplo, al importar documentos para generar embeddings, solo enviaría a la cola los documentos que realmente se han creado. También sirve para sincronizaciones periódicas en las que repetir una tanda debe ser seguro sin llenar el código de comprobaciones previas.

La letra pequeña

Esto no devuelve las filas que ya estaban en la tabla: devuelve las que la operación de inserción retorna. Si necesitas reunir tanto los registros nuevos como los existentes, tendrás que plantear una consulta adicional o un flujo distinto. Confundir ambos casos es una forma rápida de dejar registros sin procesar en una sincronización.

Tampoco sustituye a las restricciones de la base de datos. uniqueBy expresa qué conflictos queremos ignorar, pero la garantía de no duplicar datos depende de que el esquema tenga la clave única adecuada. Antes de adoptarlo en un importador crítico, comprobaría el SQL generado y el comportamiento con el motor de base de datos que use la aplicación.

Por último, no usaría esta llamada como excusa para ignorar cualquier error de escritura. Un conflicto esperado por una clave única no es lo mismo que una conexión caída, un tipo de dato inválido o una migración incompleta. La idempotencia debe estar diseñada en el modelo de datos y probada con reintentos reales del job.

Lo que dejo fuera por ahora

La nota de la versión menciona otros cambios relacionados con esquemas y con workers de cola. Sin embargo, en la referencia API oficial disponible no aparecen todavía esos métodos ni los nuevos datos del evento con una firma verificable. Prefiero no publicar ejemplos de una interfaz que no puedo contrastar en la documentación oficial.


Fuente: Laravel Framework v13.30.1 Documentación oficial: Illuminate Database Query Builder