Esse é um problema que passei horas pesquisando no passado. Parece-me algo que deveria ter sido resolvido por soluções modernas de RDBMS , mas ainda não encontrei nada que realmente atenda ao que considero uma necessidade incrivelmente comum em qualquer aplicativo da Web ou Windows com um back-end de banco de dados.
Falo de classificação dinâmica. No meu mundo de fantasia, deve ser tão simples quanto algo como:
ORDER BY @sortCol1, @sortCol2
Este é o exemplo canônico dado pelos desenvolvedores iniciantes de SQL e Stored Procedure em todos os fóruns da Internet. "Por que isso não é possível?" eles perguntaram. Invariavelmente, alguém eventualmente os ensina sobre a natureza compilada dos procedimentos armazenados, dos planos de execução em geral e todos os outros motivos pelos quais não é possível colocar um parâmetro diretamente em uma ORDER BY
cláusula.
Sei o que alguns de vocês já estão pensando: "Deixe o cliente fazer a classificação, então". Naturalmente, isso descarrega o trabalho do seu banco de dados. No entanto, no nosso caso, nossos servidores de banco de dados nem sequer estão esgotando-se 99% do tempo e ainda nem são multi-core ou qualquer uma das inúmeras melhorias na arquitetura do sistema que acontecem a cada 6 meses. Por esse motivo, ter nossos bancos de dados manipulando a classificação não seria um problema. Além disso, os bancos de dados são muitobom em classificação. Eles são otimizados para isso e levaram anos para acertar, a linguagem para fazê-lo é incrivelmente flexível, intuitiva e simples e, acima de tudo, qualquer gravador iniciante em SQL sabe como fazê-lo e, mais importante ainda, sabe como editá-lo, faça alterações, faça manutenção etc. Quando seus bancos de dados estão longe de serem tributados e você apenas deseja simplificar (e reduzir!) o tempo de desenvolvimento, isso parece ser uma escolha óbvia.
Depois, há o problema da web. Eu brinquei com o JavaScript que fará a classificação de tabelas HTML do lado do cliente, mas elas inevitavelmente não são flexíveis o suficiente para minhas necessidades e, novamente, como meus bancos de dados não são excessivamente tributados e podem fazer a classificação com muita facilidade, eu dificilmente justificarei o tempo que levaria para reescrever ou classificar meu próprio classificador JavaScript. O mesmo geralmente vale para a classificação do servidor, embora provavelmente já seja muito preferida ao JavaScript. Eu não sou um que gosta particularmente da sobrecarga de DataSets, então me processe.
Mas isso traz de volta o argumento de que não é possível - ou melhor, não é fácil. Eu fiz, com sistemas anteriores, uma maneira incrivelmente invasiva de obter classificação dinâmica. Não era bonito, nem intuitivo, simples ou flexível e um gravador de SQL iniciante seria perdido em segundos. Isso já parece não ser tanto uma "solução", mas uma "complicação".
Os exemplos a seguir não pretendem expor nenhum tipo de prática recomendada, bom estilo de codificação ou qualquer outra coisa, nem são indicativos de minhas habilidades como programador de T-SQL. Eles são o que são e eu admito plenamente que eles são confusos, de má forma e simples hack.
Passamos um valor inteiro como parâmetro para um procedimento armazenado (vamos chamar o parâmetro apenas de "classificação") e, a partir disso, determinamos várias outras variáveis. Por exemplo ... digamos que sort seja 1 (ou o padrão):
DECLARE @sortCol1 AS varchar(20)
DECLARE @sortCol2 AS varchar(20)
DECLARE @dir1 AS varchar(20)
DECLARE @dir2 AS varchar(20)
DECLARE @col1 AS varchar(20)
DECLARE @col2 AS varchar(20)
SET @col1 = 'storagedatetime';
SET @col2 = 'vehicleid';
IF @sort = 1 -- Default sort.
BEGIN
SET @sortCol1 = @col1;
SET @dir1 = 'asc';
SET @sortCol2 = @col2;
SET @dir2 = 'asc';
END
ELSE IF @sort = 2 -- Reversed order default sort.
BEGIN
SET @sortCol1 = @col1;
SET @dir1 = 'desc';
SET @sortCol2 = @col2;
SET @dir2 = 'desc';
END
Você já pode ver como se eu declarasse mais variáveis @colX para definir outras colunas nas quais eu poderia realmente ser criativo com as colunas para classificar com base no valor de "sort" ... para usá-lo, geralmente ele se parece com o seguinte cláusula incrivelmente bagunçada:
ORDER BY
CASE @dir1
WHEN 'desc' THEN
CASE @sortCol1
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END DESC,
CASE @dir1
WHEN 'asc' THEN
CASE @sortCol1
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END,
CASE @dir2
WHEN 'desc' THEN
CASE @sortCol2
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END DESC,
CASE @dir2
WHEN 'asc' THEN
CASE @sortCol2
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END
Obviamente, este é um exemplo muito simples. O material real, já que geralmente temos quatro ou cinco colunas para apoiar a classificação, cada uma com uma possível coluna secundária ou mesmo uma terceira coluna, além dessa (por exemplo, data descendente e, em seguida, classificada secundariamente por nome ascendente) e cada bi- classificação direcional que efetivamente dobra o número de casos. Sim ... fica peludo muito rápido.
A idéia é que se poderia "facilmente" alterar os casos de classificação, de modo que o veículo seja classificado antes do tempo de armazenamento ... mas a pseudo-flexibilidade, pelo menos neste exemplo simples, termina aí. Essencialmente, cada caso que falha em um teste (porque nosso método de classificação não se aplica a ele desta vez) gera um valor NULL. E, assim, você acaba com uma cláusula que funciona da seguinte maneira:
ORDER BY NULL DESC, NULL, [storagedatetime] DESC, blah blah
Você entendeu a ideia. Funciona porque o SQL Server efetivamente ignora valores nulos em ordem por cláusulas. Isso é incrivelmente difícil de manter, como qualquer pessoa com qualquer conhecimento básico de SQL pode ver. Se eu perdi algum de vocês, não se sinta mal. Demorou muito tempo para fazê-lo funcionar e ainda ficamos confusos ao tentar editá-lo ou criar novos como ele. Felizmente, não precisa mudar com frequência, caso contrário, rapidamente se tornaria "não vale a pena".
No entanto, fez trabalho.
Minha pergunta é então: existe uma maneira melhor?
Estou bem com outras soluções além das de Procedimento Armazenado, pois sei que pode não ser o caminho a seguir. De preferência, eu gostaria de saber se alguém pode fazê-lo melhor dentro do Procedimento Armazenado, mas, se não, como todos conseguem deixar o usuário classificar dinamicamente tabelas de dados (bidirecionalmente também) com o ASP.NET?
E obrigado por ler (ou pelo menos escanear) uma pergunta tão longa!
PS: Fique feliz por não ter mostrado meu exemplo de procedimento armazenado que oferece suporte à classificação dinâmica, filtragem dinâmica / pesquisa de texto de colunas, paginação via ROWNUMBER () OVER, E tente ... capturar com reversão de transação em erros ... "tamanho gigante" nem começa a descrevê-los.
Atualizar:
- Eu gostaria de evitar SQL dinâmico . A análise de uma cadeia de caracteres em conjunto e a execução de um EXEC anula muito a finalidade de ter um procedimento armazenado em primeiro lugar. Às vezes me pergunto se os contras de fazer uma coisa dessas não valeriam a pena, pelo menos nesses casos especiais de classificação dinâmica. Ainda assim, sempre me sinto suja sempre que faço seqüências de caracteres dinâmicas SQL como essa - como se ainda estivesse vivendo no mundo ASP clássico.
- Muitas das razões pelas quais queremos procedimentos armazenados em primeiro lugar são por segurança . Não atendo a questões de segurança, apenas sugiro soluções. Com o SQL Server 2005, podemos definir permissões (por usuário, se necessário) no nível do esquema nos procedimentos armazenados individuais e, em seguida, negar qualquer consulta diretamente nas tabelas. Criticar os prós e os contras dessa abordagem talvez seja para outra questão, mas, novamente, não é minha decisão. Eu sou apenas o macaco do código principal. :)