Banco de Dados

6 fev, 2013

Executando declarações UPDATE com chaves primárias compostas

Publicidade

Recentemente fui procurado por um colega desenvolvedor para ajudá-lo a resolver um problema que, segundo ele, estava lhe tirando o sono.

O problema parecia muito simples à primeira vista: era necessário escrever uma declaração SQL para atualizar um campo Matched caso um ID de usuário aparecesse em duas tabelas que eram alimentadas com dados de sistemas diferentes.

A coisa parecia elementar, até que meu colega observou um detalhe: as tais tabelas possuíam chaves primárias compostas. Isto é, a chave primária era definida por uma combinação de colunas ao invés de um única coluna, como acontece no caso das chaves primárias simples. E este detalhe faz muita diferença na implementação da solução.

Alguns SGBDs, como o SQL SERVER, suportam o uso de junções de tabelas dentro de declarações UPDATE, o que facilita em alguns casos especiais. Mas a maioria dos SGBDs que eu conheço não aceita esta sintaxe.

Vejamos alguns exemplos de sintaxe.

Digamos que o problema descrito acima envolvesse apenas chaves simples (a coluna UserID). Se eu usasse o SQL SERVER, poderia escrever assim:

--sintaxe SQL SERVER
 UPDATE MyTable
 SET Matched = 1
 FROM MytTable M INNER JOIN OtherTable O ON M.UserID = O.UserID

Uma maneira mais usual de se escrever isso e que seria aceita DB2, ORACLE, POSTGRES e vários outros, seria usando o operador IN na cláusula WHERE.

-- sintaxe genérica (DB2, ORACLE, SQL SERVER, POSTGRES...)
 UPDATE MyTable
 SET Matched = 1
 WHERE UserID IN (SELECT UserID FROM OtherTable)

O problema é que esta solução só funciona quando temos uma única coluna na cláusula WHERE. Ou seja, isso não resolveria o problema de uma tabela com chave primária composta. E como nosso SGBD não suportava uso de JOIN na declaração de UPDATE, tive que encontrar outra estratégia.

Na solução que eu propus para o problema inclui a cláusula WHERE, mas usa outro operador: EXISTS. Este operador é pouco usado e muitas vezes esquecido. Mas cai como uma luva na solução deste problema.

O EXISTS testa se uma subconsulta retorna algum registro. Se retornar 1 ou mais registros, o resultado é VERDADEIRO. Se for vazia, o resultado é FALSO.

No caso em questão, as tabelas envolvidas (MyTable e OtherTable) possuem chaves primárias compostas por dois campos : UserID e Account. A ideia é combinar as chaves das duas tabelas dentro da subconsulta.

Com estas informações, não foi difícil montar uma nova declaração UPDATE, como mostro a seguir:

 UPDATE MyTable M
 SET M.Matched = 1
 WHERE EXISTS (
 SELECT 1
 FROM OtherTable O
 WHERE O.UserID= M.UserID
 AND O.Account = M.Account
 )

Um detalhe: com a consulta acima faz um teste baseado na chave primária das tabelas, é de se esperar que o UPDATE rode com uma boa performance.

Esta solução acabou sendo implementada e a aplicação que a usa vai muito bem, obrigado.

Por hoje é só. Abraço!