Menu

Cláusula RETURNING no SQLite: INSERT, UPDATE e DELETE

Como usar a cláusula RETURNING no SQLite para recuperar as linhas afetadas por um INSERT, UPDATE ou DELETE sem precisar de uma segunda consulta.

Esta página tem editores executáveis — edite, execute e veja a saída na hora.

Um jeito de enxergar o que acabou de acontecer

Quando você executa um INSERT, UPDATE ou DELETE, o SQLite informa quantas linhas foram afetadas — mas não diz quais linhas, nem como ficaram os valores finais. A gambiarra clássica é mandar um SELECT logo depois. Isso significa duas idas ao banco, dois comandos e uma brechinha de concorrência em que alguém pode alterar a linha entre uma operação e outra.

A cláusula RETURNING no SQLite resolve isso. Você anexa ela ao comando de escrita, lista as colunas que quer de volta e o SQLite devolve as linhas afetadas como se você tivesse acabado de rodar um SELECT em cima delas:

Uma instrução, uma ida ao banco, e você já recebe de volta o id gerado e o valor padrão de created_at que o próprio banco preencheu para você.

A cláusula RETURNING foi adicionada no SQLite 3.35.0 (março de 2021). Se a sua instrução for rejeitada com erro de sintaxe, dê uma olhada em SELECT sqlite_version(); — versões mais antigas não reconhecem essa palavra-chave.

Recuperando o ID gerado com INSERT RETURNING

O motivo mais comum para usar RETURNING no SQLite é justamente pegar a chave primária autogerada logo após o insert:

Antes da cláusula RETURNING, o jeito era fazer o INSERT e depois chamar last_insert_rowid() (ou o equivalente do seu driver) na mesma conexão. Isso ainda funciona, mas depende do estado da conexão — algo fácil de quebrar quando você usa pool de conexões ou múltiplas threads. Já o RETURNING id é explícito, fica preso ao próprio comando e se comporta da mesma maneira independentemente de quem está gerenciando a conexão.

Se a sua tabela não declara um INTEGER PRIMARY KEY explícito, dá pra recuperar o identificador implícito da linha do mesmo jeito:

Toda tabela comum no SQLite tem um rowid, e o RETURNING devolve ele pra você sem complicação.

Várias colunas e expressões

A cláusula RETURNING aceita o mesmo formato da lista de colunas de um SELECT. Você pode listar colunas, usar *, montar expressões e dar apelidos a elas:

RETURNING * é útil quando você quer trazer tudo de uma vez — inclusive os valores padrão que o banco preencheu — sem precisar listar cada coluna:

Você verá o novo id, o name que você passou e o timestamp que o SQLite calculou.

RETURNING com UPDATE

Em um UPDATE, a cláusula RETURNING devolve os valores já atualizados — ou seja, a linha como ela ficou depois das suas alterações:

Você recebe de volta o novo saldo da Ada, que é 125 — não o antigo 100. É justamente isso que torna o RETURNING perfeito para contadores atômicos e operações de débito/crédito: você não precisa ler, calcular, gravar e ler de novo.

Se o WHERE casar com várias linhas, você recebe uma linha de retorno para cada linha afetada:

Três linhas inseridas, três linhas devolvidas. A ordem não é garantida — se você precisar de uma ordem específica, ordene o resultado no lado do cliente.

RETURNING com DELETE

No DELETE, o RETURNING devolve as linhas como elas estavam logo antes da exclusão. Isso é útil para arquivamento, trilhas de auditoria ou simplesmente para confirmar o que foi removido:

Você recebe de volta as duas sessões expiradas com todos os campos intactos, mesmo que elas não existam mais na tabela. Se a ideia for movê-las para outro lugar, esse é o cenário perfeito para uma tabela de arquivo — basta ler o resultado e inserir em outro lugar dentro da mesma transação.

RETURNING com UPSERT

A cláusula RETURNING também funciona com INSERT ... ON CONFLICT ... DO UPDATE. A linha retornada reflete qual caminho foi executado — o insert novo ou o update do conflito:

Execute esse comando duas vezes. Na primeira, ele faz o INSERT e devolve ('visits', 1). Na segunda, o conflito é acionado, o valor é incrementado e você recebe ('visits', 2). Em ambos os casos, um comando entra e uma linha sai — sem precisar perguntar "foi insert ou update?" antes de seguir adiante.

Esse é o padrão mais limpo do SQLite para "me devolva o valor atual, criando se preciso", sem ficar indo e voltando ao banco.

Alguns detalhes que vale saber

Algumas pegadinhas que costumam pegar a galera de surpresa:

  • O RETURNING sempre enxerga a linha depois da alteração no caso de INSERT e UPDATE, e antes da alteração no DELETE. Não existe sintaxe para pedir o outro lado.
  • A ordem das linhas retornadas não é garantida. Se isso importa, adicione um ORDER BY no lado do cliente.
  • Você não pode usar RETURNING dentro de uma subquery. É uma cláusula de nível superior no comando de escrita, não uma expressão.
  • O RETURNING não devolve os dados modificados pelos triggers BEFORE — ele retorna os valores que realmente foram gravados. Os triggers AFTER rodam entre a escrita e a devolução da linha.
  • Colunas geradas e valores DEFAULT aparecem no resultado. É justamente por isso que RETURNING * é uma forma rápida de conferir o que o banco preencheu para você.

A seguir: importando dados de CSV

A cláusula RETURNING é ótima quando você está gravando uma linha ou um punhado delas e quer ver o resultado na hora. Quando o cenário é carregar milhares de linhas a partir de um arquivo, o caminho são as ferramentas de importação de CSV do SQLite — assunto da próxima página.

Perguntas frequentes

O SQLite suporta a cláusula RETURNING?

Sim, a partir da versão 3.35.0 (lançada em março de 2021). Dá pra acrescentar RETURNING em comandos INSERT, UPDATE e DELETE para receber de volta as linhas afetadas. Se você estiver numa versão mais antiga, o parser vai recusar — confira com SELECT sqlite_version();.

Como pegar o ID de uma linha recém-inserida no SQLite?

Use INSERT ... RETURNING id (ou RETURNING rowid se a tabela não tiver chave primária explícita). O valor gerado volta junto com o próprio comando, então você não precisa fazer uma segunda chamada com last_insert_rowid().

Dá para retornar mais de uma coluna com RETURNING?

Dá sim. É só listar as colunas separadas por vírgula, igualzinho num SELECT: RETURNING id, name, created_at. Você também pode usar RETURNING * para trazer todas as colunas, ou escrever expressões como RETURNING id, price * quantity AS total.

RETURNING funciona com UPSERT e ON CONFLICT?

Funciona. O INSERT ... ON CONFLICT ... DO UPDATE ... RETURNING ... devolve a linha tanto se ela foi inserida agora quanto se foi atualizada pela resolução de conflito. É a forma mais limpa de fazer um upsert e já ler o estado final em uma única ida ao banco.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR