← Voltar ao blog

UPDATE com JOIN no SQL: sintaxe em cada banco e armadilhas

UPDATE com JOIN muda de sintaxe em MySQL, PostgreSQL, SQL Server e Firebird. Veja o comando certo em cada um e como testar antes de rodar em produção.


Atualizar uma tabela com dados de outra não tem sintaxe única: MySQL usa UPDATE ... JOIN, PostgreSQL usa UPDATE ... FROM, SQL Server usa UPDATE ... FROM com alias e o Firebird não tem junção no UPDATE — resolve com EXISTS ou subconsulta. MERGE é o caminho padrão quando o banco suporta.

O comando muda de banco para banco, mas a armadilha é a mesma em todos: junção que multiplica linha pode atualizar o registro errado sem dar erro. Este guia mostra a sintaxe certa em cada um e como testar antes de rodar.


Como atualizar uma tabela usando dados de outra?

Com uma junção que restrinja as linhas e uma condição que garanta uma linha de origem por linha de destino. Sem essa garantia, o banco escolhe um valor entre os candidatos e segue em frente.

-- intenção: marcar como bloqueado o pedido de cliente inativo
UPDATE PEDIDOS P
SET P.STATUS = 'B'
WHERE P.IDCLIENTE IN (
  SELECT C.ID FROM CLIENTES C WHERE C.ATIVO = 0
);

Essa forma com IN funciona em praticamente todos os bancos. As variações por dialeto servem para trazer mais de uma coluna da origem ou para usar junção explícita.

MySQL

UPDATE PEDIDOS P
JOIN CLIENTES C ON C.ID = P.IDCLIENTE
SET P.STATUS = 'B',
    P.OBSERVACAO = CONCAT('cliente inativo em ', DATE_FORMAT(NOW(), '%d/%m/%Y'))
WHERE C.ATIVO = 0;

O MySQL aceita JOIN, LEFT JOIN e ORDER BY ... LIMIT no UPDATE (útil para atualizar em lote). Cuidado com LEFT JOIN: linha sem par recebe NULL e sobrescreve o que estava lá.

PostgreSQL

UPDATE PEDIDOS P
SET STATUS = 'B',
    OBSERVACAO = 'cliente inativo em ' || to_char(now(), 'DD/MM/YYYY')
FROM CLIENTES C
WHERE C.ID = P.IDCLIENTE
  AND C.ATIVO = false;

No PostgreSQL, a tabela do FROM não pode ser a mesma que está sendo atualizada sem alias. E se o FROM casar com mais de uma linha, o resultado é indefinido — o banco não avisa.

SQL Server

UPDATE P
SET P.STATUS = 'B'
FROM PEDIDOS P
JOIN CLIENTES C ON C.ID = P.IDCLIENTE
WHERE C.ATIVO = 0;

Aqui o alias do UPDATE é obrigatório: UPDATE P ... FROM PEDIDOS P. Escrever UPDATE PEDIDOS ... FROM PEDIDOS P gera erro de ambiguidade.

Firebird

O Firebird não aceita JOIN no UPDATE. Duas formas seguras:

-- forma 1: EXISTS, boa para restringir linhas
UPDATE PEDIDOS P
SET P.STATUS = 'B'
WHERE EXISTS (
  SELECT 1 FROM CLIENTES C
  WHERE C.ID = P.IDCLIENTE
    AND C.ATIVO = 0
);
-- forma 2: subconsulta escalar, quando precisa trazer um valor da origem
UPDATE PEDIDOS P
SET P.OBSERVACAO = (
  SELECT C.NOME FROM CLIENTES C WHERE C.ID = P.IDCLIENTE
)
WHERE EXISTS (
  SELECT 1 FROM CLIENTES C WHERE C.ID = P.IDCLIENTE AND C.ATIVO = 0
);

A subconsulta escalar precisa devolver zero ou uma linha. Se devolver duas, o Firebird levanta erro — o que é melhor do que atualizar com valor arbitrário.

Qual sintaxe usar em cada banco?

Banco Forma Cuidado
MySQL UPDATE ... JOIN ... SET aceita ORDER BY/LIMIT; LEFT JOIN sobrescreve com NULL
PostgreSQL UPDATE ... SET ... FROM tabela-alvo não pode aparecer no FROM sem alias
SQL Server UPDATE alias ... FROM alias do UPDATE é obrigatório
Firebird UPDATE + EXISTS ou subconsulta não existe JOIN dentro do UPDATE
Padrão SQL MERGE Firebird 2.1+, PostgreSQL 15+, MySQL via ON DUPLICATE KEY

Por que a junção pode atualizar a linha errada?

Porque junção não é filtro: é multiplicação. Se CLIENTES tiver dois registros com o mesmo ID (dado duplicado, chave ausente), o UPDATE encontra duas linhas de origem para o mesmo pedido.

-- detecta origem duplicada antes de rodar o UPDATE
SELECT C.ID, COUNT(*) AS QTD
FROM CLIENTES C
GROUP BY C.ID
HAVING COUNT(*) > 1;

Em banco que aceita junção no UPDATE, como o MySQL, o comando executa e escolhe um dos valores. Em PostgreSQL, o resultado é indefinido. Nenhum dos dois avisa. Por isso a chave de junção precisa ser única — e, se não for, o problema é o dado, não o SQL.

Como testar antes de rodar?

Três passos que custam um minuto e evitam noite em claro:

  1. Rode o mesmo WHERE em um SELECT e confira a contagem.
  2. Confirme que a contagem bate com o que você espera atualizar.
  3. Execute dentro de transação e confira o resultado antes do COMMIT.
-- teste: quantas linhas serão afetadas?
SELECT COUNT(*)
FROM PEDIDOS P
WHERE P.STATUS <> 'B'
  AND P.IDCLIENTE IN (SELECT C.ID FROM CLIENTES C WHERE C.ATIVO = 0);
-- execução controlada (Firebird/isql)
SET TRANSACTION READ WRITE;
UPDATE PEDIDOS P
SET P.STATUS = 'B'
WHERE P.IDCLIENTE IN (SELECT C.ID FROM CLIENTES C WHERE C.ATIVO = 0);

-- conferir
SELECT STATUS, COUNT(*) FROM PEDIDOS GROUP BY STATUS;

ROLLBACK; -- ou COMMIT, depois de conferir

No MySQL, SET SQL_SAFE_UPDATES = 1 bloqueia UPDATE sem chave no WHERE — útil como cinto de segurança em ambiente de desenvolvimento.

E quando o destino é outra tabela, não a mesma linha?

Para sincronizar tabela de resumo ou histórico, MERGE é mais legível que UPDATE com junção e evita o padrão "tenta update, se não existir insere".

MERGE INTO RESUMO_CLIENTE R
USING (
  SELECT IDCLIENTE, COUNT(*) AS QTD, SUM(VALOR) AS TOTAL
  FROM LANCAMENTOS
  GROUP BY IDCLIENTE
) L
ON R.IDCLIENTE = L.IDCLIENTE
WHEN MATCHED THEN
  UPDATE SET R.QTD = L.QTD, R.TOTAL = L.TOTAL
WHEN NOT MATCHED THEN
  INSERT (IDCLIENTE, QTD, TOTAL) VALUES (L.IDCLIENTE, L.QTD, L.TOTAL);

MERGE existe no Firebird 2.1+, no SQL Server, no Oracle e no PostgreSQL 15+. No MySQL, o equivalente é INSERT ... ON DUPLICATE KEY UPDATE.

Passo a passo para um UPDATE com junção seguro

  1. Escreva o SELECT equivalente e confira as linhas.
  2. Verifique se a chave de junção é única.
  3. Restrinja o WHERE o máximo possível.
  4. Execute em transação e confira a contagem.
  5. Faça COMMIT só depois da conferência.
  6. Guarde o SELECT de verificação para reutilizar em produção.
  7. Monitore tempo: UPDATE em massa pode segurar lock em tabela grande.

Checklist

  • A chave de junção é única na origem.
  • O SELECT de conferência foi rodado antes.
  • O WHERE restringe só o que deve mudar.
  • O comando usa a sintaxe do banco em uso.
  • A execução está em transação com conferência.
  • Existe backup ou ponto de restore.
  • Tabela grande tem janela de manutenção definida.

Perguntas frequentes

Qual a sintaxe de UPDATE com JOIN no Firebird?

O Firebird não aceita JOIN no UPDATE. Use WHERE EXISTS (SELECT 1 FROM ...) para restringir as linhas ou uma subconsulta escalar no SET para trazer o valor.

UPDATE com JOIN pode atualizar a mesma linha duas vezes?

Não duas vezes, mas pode escolher entre valores diferentes quando a origem tem mais de uma linha para a mesma chave. O resultado é indefinido em PostgreSQL e arbitrário em MySQL.

Como desfazer um UPDATE errado?

Com ROLLBACK, se a transação ainda estiver aberta. Depois do COMMIT, só com backup ou com o valor anterior registrado em tabela de auditoria — e é por isso que a conferência antes do commit não é opcional.

Conclusão

UPDATE com junção é um comando de escrita: a sintaxe muda por banco, mas o risco é comum a todos. Chave única, WHERE restrito, SELECT de conferência e transação cobrem quase todo acidente. O resto é backup.

Quando a query é o gargalo, o Sqlify gera o SQL a partir da descrição em português e explica cada cláusula — inclusive as junções que costumam multiplicar linha.

Leia também: