Una forma de ver lo que acaba de pasar
Cuando ejecutas un INSERT, UPDATE o DELETE, SQLite te dice cuántas filas se vieron afectadas, pero no cuáles ni cómo quedaron sus valores finales. El truco de toda la vida es lanzar un SELECT justo después. Eso supone dos viajes de ida y vuelta, dos sentencias y una pequeña ventana de carrera en la que otra persona podría modificar la fila entre medias.
La cláusula RETURNING resuelve esto. La añades al final de una sentencia de escritura, indicas las columnas que quieres recuperar y SQLite te devuelve las filas afectadas como si acabaras de hacer un SELECT sobre ellas:
Una sentencia, un viaje y obtienes de vuelta el id generado junto con el valor por defecto de created_at que la base de datos rellenó por ti.
La cláusula RETURNING se incorporó en SQLite 3.35.0 (marzo de 2021). Si tu sentencia te devuelve un error de sintaxis, revisa primero SELECT sqlite_version(); — las versiones antiguas no reconocen la palabra clave.
Obtener el ID generado tras un INSERT
El motivo más habitual para recurrir a RETURNING en SQLite es justamente ese: recuperar la clave primaria autogenerada justo después de insertar una fila:
Antes de la cláusula RETURNING, lo habitual era hacer el INSERT y luego llamar a last_insert_rowid() (o el equivalente de tu driver) sobre la misma conexión. Sigue funcionando, claro, pero depende del estado de la conexión —algo fácil de romper cuando entran en juego pools de conexiones o varios hilos. En cambio, RETURNING id es explícito, vive dentro de la propia sentencia y se comporta igual sin importar quién gestione la conexión.
Si tu tabla no declara una INTEGER PRIMARY KEY de forma explícita, aún puedes obtener el identificador implícito de la fila:
Toda tabla común de SQLite tiene un rowid, y RETURNING te lo devuelve sin problema.
Varias columnas y expresiones
RETURNING admite lo mismo que la lista de columnas de un SELECT. Puedes enumerar columnas, usar *, armar expresiones y ponerles alias:
RETURNING * te resulta muy práctico cuando quieres obtener todo —incluidos los valores por defecto que rellena la base de datos— sin tener que nombrar cada columna:
Verás el nuevo id, el name que pasaste y el timestamp que SQLite calculó.
RETURNING con UPDATE
En un UPDATE, la cláusula RETURNING te devuelve los valores ya actualizados, es decir, la fila tal como queda después de aplicar tus cambios:
Recibes el nuevo saldo de Ada, que es 125, y no el anterior de 100. Por eso la cláusula RETURNING viene de maravilla para contadores atómicos y operaciones de débito/crédito: te ahorras tener que leer, calcular, escribir y volver a leer.
Si el WHERE coincide con varias filas, obtendrás una fila de respuesta por cada fila afectada:
Tres filas entran, tres filas salen. El orden no está garantizado: si necesitas un orden concreto, ordena el resultado en el cliente.
DELETE RETURNING en SQLite
En un DELETE, la cláusula RETURNING te devuelve las filas tal y como estaban justo antes de borrarlas. Viene genial para archivar datos, llevar un registro de auditoría o simplemente confirmar qué se eliminó:
Recibes las dos sesiones expiradas con todos sus campos intactos, aunque ya no existan en la tabla. Si quieres moverlas a otro lugar, es el escenario ideal para una tabla de archivo: lees el resultado y lo insertas en otra tabla dentro de la misma transacción.
RETURNING con UPSERT en SQLite
La cláusula RETURNING también funciona con INSERT ... ON CONFLICT ... DO UPDATE. La fila devuelta refleja la rama que se ejecutó, ya sea la inserción nueva o la actualización por conflicto:
Ejecuta esa sentencia dos veces. La primera vez inserta y devuelve ('visits', 1). La segunda vez se dispara el conflicto, el valor se incrementa y obtienes ('visits', 2). En ambos casos, una sola sentencia y una sola fila de salida: no hace falta preguntarse "¿se insertó o se actualizó?" antes de seguir.
Este es el patrón más limpio en SQLite para "dame el valor actual, creándolo si hace falta" sin necesidad de un ida y vuelta extra.
Algunas cosas que conviene saber
Hay un puñado de detalles que suelen pillar a la gente desprevenida:
RETURNINGsiempre ve la fila después del cambio enINSERTyUPDATE, y antes del cambio enDELETE. No hay sintaxis para pedir el otro lado.- El orden de las filas devueltas no está garantizado. Si te importa, añade un
ORDER BYdesde el cliente. - No puedes meter
RETURNINGdentro de una subconsulta. Es una cláusula de nivel superior de la sentencia de escritura, no una expresión. RETURNINGno devuelve los datos modificados por los triggersBEFORE— devuelve los valores que realmente se escribieron. Los triggersAFTERse ejecutan entre la escritura y el momento en que la fila se devuelve.- Las columnas generadas y los valores
DEFAULTaparecen en el resultado. Justamente por eso,RETURNING *es una forma rápida de ver qué rellenó la base de datos por ti.
Siguiente paso: importar datos desde CSV
RETURNING viene de perlas cuando escribes una fila o unas pocas a la vez y quieres ver el resultado al instante. Pero cuando tengas que cargar miles de filas desde un archivo, lo suyo es tirar de las herramientas de importación CSV de SQLite — y de eso hablamos en la siguiente página.
Preguntas frecuentes
¿SQLite admite la cláusula RETURNING?
Sí, desde la versión 3.35.0 (publicada en marzo de 2021). Puedes añadir RETURNING al final de un INSERT, UPDATE o DELETE para obtener las filas afectadas. Si trabajas con una versión anterior, el parser la rechazará directamente; compruébalo con SELECT sqlite_version();.
¿Cómo obtengo el ID de una fila recién insertada en SQLite?
Usa INSERT ... RETURNING id (o RETURNING rowid si la tabla no tiene clave primaria explícita). Te devuelve el valor generado dentro de la misma sentencia, así que te ahorras la segunda consulta a last_insert_rowid().
¿Puede RETURNING devolver más de una columna?
Sí. Indica las columnas separadas por comas, igual que en un SELECT: RETURNING id, name, created_at. También puedes usar RETURNING * para traerte todas, o escribir expresiones del tipo RETURNING id, price * quantity AS total.
¿Funciona RETURNING con UPSERT y ON CONFLICT?
Sí. INSERT ... ON CONFLICT ... DO UPDATE ... RETURNING ... te devuelve la fila tanto si se insertó por primera vez como si se actualizó al resolver el conflicto. Es la forma más limpia de hacer un upsert y leer el resultado en un solo viaje a la base de datos.