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():
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.
Quando voce insere uma linha e logo quer o seu
idgerado sem disparar uma segunda consulta, a clausulaRETURNINGe a resposta. Essa extensao do PostgreSQL transformaINSERT,UPDATEeDELETEem comandos que devolvem linhas — igual a umSELECT, 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
idpara linhas relacionadas. SemRETURNINGvoce roda o insert e depois umSELECTseparado (ou chamacurrval), perdendo um round-trip ate o banco e arriscando uma corrida. ORETURNINGdevolve os valores direto do mesmo comando.INSERT INTO users (email, name, country) VALUES ('a@example.com', 'Ann', 'DE') RETURNING id, created_at;Propriedades principais:
INSERT,UPDATEeDELETE.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
RETURNINGpode 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
DELETEtambem 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
RETURNINGem uma expressao de tabela comum (CTE) e usa-lo no passo seguinte. Assim se monta uma cadeia atomica "insere o pai, pega o seuid, 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:
DELETE ... RETURNING *dentro de um CTE e depoisINSERTdo resultado em uma tabela de arquivo.ide uma contagem de linhas afetadas comcount(*)sobre o CTE.Gotcha: RETURNING ve o novo, e o MySQL nao tem
Algumas armadilhas para ficar de olho:
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.RETURNINGnao e garantida. Se precisar de ordem, envolva em um CTE e ordene com umSELECT ... ORDER BYexterno.RETURNINGdevolve apenas as linhas realmente gravadas. ComINSERT ... ON CONFLICT DO NOTHING, uma linha pulada por conflito nao e devolvida de jeito nenhum.RETURNING(o MariaDB 10.5+ suporta, paraINSERTeDELETE). No MySQL, leia o auto-incremento apos o insert comLAST_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, oRETURNINGeconomiza um round-trip e fecha corridas; no MySQL, tenha oLAST_INSERT_ID()a mao.