sqlpostgresqlreturninginsert

RETURNING no PostgreSQL: pegar ids e linhas alteradas sem um segundo round-trip

Como a clausula RETURNING em INSERT/UPDATE/DELETE devolve ids gerados e colunas alteradas em um unico round-trip, sem um SELECT extra.

3 min de leituraReferencesql · postgresql · returning · insert · cte · mysql

Quando voce insere uma linha e logo quer o seu id gerado sem disparar uma segunda consulta, a clausula RETURNING e a resposta. Essa extensao do PostgreSQL transforma INSERT, UPDATE e DELETE em comandos que devolvem linhas — igual a um SELECT, mas sobre os dados que voce acabou de gravar.

Por que o RETURNING importa

O caso classico: voce insere um usuario e precisa do seu id para linhas relacionadas. Sem RETURNING voce roda o insert e depois um SELECT separado (ou chama currval), perdendo um round-trip ate o banco e arriscando uma corrida. O RETURNING devolve os valores direto do mesmo comando.

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

Propriedades principais:

  • Funciona em INSERT, UPDATE e DELETE.
  • Devolve uma linha por linha afetada, nao apenas a primeira.
  • A lista aceita qualquer coluna, expressoes sobre elas e aliases.

RETURNING * e colunas calculadas

Quando voce quer todas as colunas da linha resultante — incluindo os valores padrao e o que os triggers preencheram — use RETURNING *. Isso e bem util quando alguns campos sao preenchidos do lado do banco.

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

A lista do RETURNING pode conter expressoes, nao apenas colunas isoladas:

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

O DELETE tambem pode devolver o que removeu — uma auditoria pronta do que exatamente sumiu:

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

RETURNING dentro de um CTE

O uso mais poderoso e alimentar o resultado do RETURNING em uma expressao de tabela comum (CTE) e usa-lo no passo seguinte. Assim se monta uma cadeia atomica "insere o pai, pega o seu id, insere os filhos" em uma unica 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;

O mesmo truque resolve "atualiza e registra o historico": capture os valores antigos com uma subconsulta e guarde-os ao lado dos novos.

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;

Padroes uteis:

  • Mover linhas: DELETE ... RETURNING * dentro de um CTE e depois INSERT do resultado em uma tabela de arquivo.
  • Devolver ao mesmo tempo um id e uma contagem de linhas afetadas com count(*) sobre o CTE.
  • Capturar o valor antigo e o novo de uma coluna em uma unica passada.

Gotcha: RETURNING ve o novo, e o MySQL nao tem

Algumas armadilhas para ficar de olho:

  • Em UPDATE ... RETURNING, as colunas ja sao os valores novos. Para capturar o antigo voce precisa de uma subconsulta ou CTE contra a tabela original; o comando sozinho nao vai mostra-lo.
  • A ordem das linhas no RETURNING nao e garantida. Se precisar de ordem, envolva em um CTE e ordene com um SELECT ... ORDER BY externo.
  • O RETURNING devolve apenas as linhas realmente gravadas. Com INSERT ... ON CONFLICT DO NOTHING, uma linha pulada por conflito nao e devolvida de jeito nenhum.
  • O MySQL nao suporta a clausula RETURNING (o MariaDB 10.5+ suporta, para INSERT e DELETE). No MySQL, leia o auto-incremento apos o insert com 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();

O ClickHouse tambem nao tem RETURNING: la os inserts sao assincronos e em lote, entao gere as chaves do lado da aplicacao. Resumindo: no PostgreSQL, o RETURNING economiza um round-trip e fecha corridas; no MySQL, tenha o LAST_INSERT_ID() a mao.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador