sábado, 2 de março de 2013

Simplificando a construção de consultas dinâmicas

Um problema recorrente é o de construir consultas para formulários com campos opcionais. Em grande parte dos casos, as consultas são geradas dinamicamente antes de serem submetidas ao banco de dados. Isto é, a lógica da consulta fica escondida na lógica da aplicação com cada cláusula sendo adicionada ou não conforme o valor digitado pelo usuário (e um if na aplicação).

Penso que, em muitos casos, isso pode ser resolvido com pequenas alterações no SQL. Apresento uma tabela de pessoas (PESSOA) como exemplo. Ela possui as seguintes colunas:
  • NOME;
  • NASCIMENTO (uma data);
  • DEPARTAMENTO (um número).
Vou começar pela data. Suponha que o usuário possa pesquisar todas as pessoas pela data de nascimento, mas que o campo é opcional. A consulta mais óbvia é:

select *
from pessoa
where nascimento=:data_nascimento
Evidentemente, se o usuário não digitar uma data, a aplicação deverá retirar a cláusula. Mas, para evitar isso, podemos reescrever a consulta assim:

select *
from pessoa
where 
nascimento between nvl(:data_nascimento, to_date('1000', 'YYYY')) 
               and nvl(:data_nascimento, to_date('3000', 'YYYY'))
Neste caso, se a data de nascimento não for informada, a consulta retornará todas as pessoas que nasceram entre o ano 1000 e o ano 3000 (a segunda data poderia muito bem ser sysdate) e isso deve bastar, a menos que se trate de uma base de dados do Vaticano. Se o formulário permite informar um período (duas datas), então a consulta já está preparada para recebê-las.

Com relação aos nomes, podemos escrever o seguinte:

select *
from pessoa
where 
nome like nvl(:nome, '%')
O que mais se vê é isto, no entanto:
(:nome is null or nome like :nome) 
Isso é um pouco mais eficiente, mas menos no estilo da programação incondicional. E, se o espírito hacker bater:

select *
from pessoa
where nome between nvl(:nome, chr(0)) and nvl(:nome, chr(255))
Entre os caracteres 0 e 255 estão todas as letras do alfabeto latino, inclusive com acentos, no código ASCII. Este mesmo truque pode ser usado para o departamento:

select *
from pessoa
where departamento between nvl(:dept, -1) and nvl(:dept, 1000)
Evidemente, todos os departementos devem ter códigos entre -1 e 1000; conhecer os dados é importante para poder usar este tipo de truque, mas isso é importante de qualquer maneira. A união de todas as cláusulas é:

select *
from pessoa
where
nome like nvl(:nome, '%') 
and nascimento between nvl(:data_nascimento, to_date('1000', 'YYYY')) 
                   and nvl(:data_nascimento, to_date('3000', 'YYYY'))
and departamento between nvl(:dept, -1) and nvl(:dept, 1000)
Agrega-se um pouco de complexidade à consulta (que pode, por outro lado, ser facilmente inserida numa procedure), mas simplifica-se o código da aplicação.

Os detratores da programação incondicional levantarão as bandeiras da legibilidade e do desempenho, mas todos sabemos que o mais importante é eliminar os ifs dos programas.

2 comentários:

  1. Vou usar isso como exemplo do que não fazer ao escrever SQL nas minhas aulas de otimização de consultas.....

    ResponderExcluir
  2. Que bom que achaste uma utilidade para a tecnologia ifless!!

    ResponderExcluir