Data

8 jul, 2011

Evitando condicionais OR com Joins

Publicidade

Texto original em
inglês disponível em
http://www.helpwithsql.com/2011/06/avoiding-or-conditions-with-joins/

?

Condicionais OR e join statements – algumas
coisas simplesmente não ficam bem juntas. Da mesma maneira como condicionais OR podem prejudicar signficamente queries com subqueries (inglês), elas podem devastar os joins também.

Pegue, por exemplo, a
query a seguir [reconhecidamente irrealista]. Ele supõe que uma tabela numbers foi criada. [Veja meu  post sobre joins cartesianos – inglês – para uma query
configurar uma tabela numbers]. Suponha que a tabela só tenha 10 mil
registros.

SELECT n1.num, n2.num, n3.num FROM numbers n1
INNER JOIN numbers n2 ON ( (n1.num = n2.num - 400) OR (n1.num = n2.num + 400))
INNER JOIN numbers n3 ON n2.num = n3.num - 300
ORDER BY n1.num, n2.num, n3.num

Essa query parece bem simples. No entanto,
apesar de a tabela fonte ter apenas 10 mil linhas e o resultado apenas 18.900
registros, ele leva 5 segundos para completar, e o Management Studio [e
francamente todo o meu computador] congela enquanto ele está sendo executado.
Essa query elevou o uso da CPU para 100%. Isso não é bom – joins e condicionais OR
simplesmente não se misturam [pelo menos no SQL Server].

Eu encontrei esse mesmo cenário no BPS.
Temos um pacote DTS para trazer os dados dos estudantes do local fonte para
outro banco de dados [com um esquema diferente], no qual as atribuições da escola são
feitas. Um dos passos levou 20 minutos, e eu simplesmente aceitei isso das
primeiras vezes em que ele foi executado. Mas como sempre devemos perguntar se existe um jeito melhor, (inglês) decidi investigar esse passo. Com um
tempo de execução de 20 minutos, eu esperava ver uma rede complexa de sql com
30 joins e 150 linhas. Eu não poderia ter errado mais – era uma query simples que fazia a mesma coisa que a de cima [com um condicional OR]. Em menos de 5 minutos, eu
quebrei a query em dois passos separados, e o que antes tinha levado 20 minutos
para executar, levou apenas 2 segundos.

Para a query acima, aqui
está uma solução usando UNION para quebrar o OR. Existem outras maneiras de
fazer isso, e eu encorajo comentários sobre outras abordagens de como remover a
OR condition.

SELECT n1.num, n2.num, n3.num FROM numbers n1
INNER JOIN numbers n2 ON n1.num = n2.num - 400
INNER JOIN numbers n3 ON n2.num = n3.num - 300
UNION
SELECT n1.num, n2.num, n3.num FROM numbers n1
INNER JOIN numbers n2 ON n1.num = n2.num + 400
INNER JOIN numbers n3 ON n2.num = n3.num - 300
ORDER BY n1.num, n2.num, n3.num

Essa query é executada instantaneamente
com uma tabela numbers contendo 10 mil linhas. Ela termina em 8 segundos
para uma tabela contendo 800 mil linhas. Se eu tivesse utilizado a tabela maior
com a primeira query, eu provavelmente teria que reiniciar o computador.