sqlpostgresqlreturninginsert

RETURNING en PostgreSQL: obtener ids y filas modificadas sin un segundo viaje

Como la clausula RETURNING en INSERT/UPDATE/DELETE devuelve los ids generados y las columnas modificadas en un solo viaje, sin un SELECT extra.

2 min de lecturaReferencesql · postgresql · returning · insert · cte · mysql

Cuando insertas una fila y enseguida quieres su id generado sin lanzar una segunda consulta, la clausula RETURNING es la respuesta. Esta extension de PostgreSQL convierte INSERT, UPDATE y DELETE en sentencias que devuelven filas — igual que un SELECT, pero sobre los datos que acabas de escribir.

Por que importa RETURNING

El caso clasico: insertas un usuario y necesitas su id para filas relacionadas. Sin RETURNING ejecutas el insert y luego un SELECT aparte (o llamas a currval), perdiendo un viaje a la base de datos y arriesgando una condicion de carrera. RETURNING devuelve los valores directamente desde la misma orden.

INSERT INTO users (email, name, country)
VALUES ('a@example.com', 'Ann', 'DE')
RETURNING id, created_at;

Propiedades clave:

  • Funciona en INSERT, UPDATE y DELETE.
  • Devuelve una fila por cada fila afectada, no solo la primera.
  • La lista admite cualquier columna, expresiones sobre ellas y alias.

RETURNING * y columnas calculadas

Cuando quieres todas las columnas de la fila resultante — incluidos los valores por defecto y lo que rellenaron los triggers — usa RETURNING *. Es muy comodo cuando algunos campos se completan del lado de la base de datos.

INSERT INTO orders (user_id, amount, status)
VALUES (42, 99.50, 'pending')
RETURNING *;

La lista de RETURNING puede contener expresiones, no solo columnas sueltas:

UPDATE orders
SET amount = amount * 1.10
WHERE status = 'pending'
RETURNING id, amount AS new_amount, round(amount / 1.10, 2) AS old_amount;

DELETE tambien puede devolver lo que elimino — una auditoria lista de lo que desaparecio exactamente:

DELETE FROM orders
WHERE status = 'cancelled'
RETURNING id, user_id, amount;

RETURNING dentro de un CTE

El uso mas potente es pasar el resultado de RETURNING a una expresion de tabla comun (CTE) y usarlo en el paso siguiente. Asi se arma una cadena atomica "inserta el padre, toma su id, inserta los hijos" en una sola consulta.

WITH new_user AS (
  INSERT INTO users (email, name, country)
  VALUES ('team@example.com', 'Team', 'ES')
  RETURNING id
)
INSERT INTO orders (user_id, amount, status)
SELECT id, 0, 'pending'
FROM new_user
RETURNING id AS order_id, user_id;

El mismo truco resuelve "actualiza y registra el historial": captura los valores viejos con una subconsulta y guardalos junto a los nuevos.

WITH bumped AS (
  UPDATE employees
  SET salary = salary * 1.05
  WHERE dept = 'eng'
  RETURNING id, salary AS new_salary
)
SELECT id, new_salary FROM bumped ORDER BY id;

Patrones utiles:

  • Mover filas: DELETE ... RETURNING * dentro de un CTE y luego INSERT del resultado en una tabla de archivo.
  • Devolver a la vez un id y un conteo de filas afectadas con count(*) sobre el CTE.
  • Capturar el valor viejo y el nuevo de una columna en una sola pasada.

Gotcha: RETURNING ve lo nuevo, y MySQL no lo tiene

Algunas trampas a vigilar:

  • En UPDATE ... RETURNING, las columnas ya son los valores nuevos. Para capturar el viejo necesitas una subconsulta o un CTE contra la tabla original; la sentencia por si sola no lo mostrara.
  • El orden de las filas en RETURNING no esta garantizado. Si necesitas orden, envuelvelo en un CTE y ordena con un SELECT ... ORDER BY externo.
  • RETURNING devuelve solo las filas realmente escritas. Con INSERT ... ON CONFLICT DO NOTHING, una fila omitida por conflicto no se devuelve en absoluto.
  • MySQL no soporta la clausula RETURNING (MariaDB 10.5+ si, para INSERT y DELETE). En MySQL, lee el autoincremento tras el insert con LAST_INSERT_ID():
-- MySQL: no RETURNING, read the generated id separately
INSERT INTO users (email, name, country)
VALUES ('a@example.com', 'Ann', 'DE');
SELECT LAST_INSERT_ID();

ClickHouse tampoco tiene RETURNING: alli los inserts son asincronos y por lotes, asi que genera las claves en el lado de la aplicacion. En resumen: en PostgreSQL, RETURNING ahorra un viaje y cierra carreras; en MySQL, ten a mano LAST_INSERT_ID().

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador