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. :)
fonte
Respostas:
Sim, é uma dor, e o jeito que você está fazendo é parecido com o que eu faço:
Isso, para mim, ainda é muito melhor do que criar SQL dinâmico a partir do código, que se transforma em um pesadelo de escalabilidade e manutenção para DBAs.
O que faço do código é refatorar a paginação e a classificação, para que eu pelo menos não tenha muita repetição lá com o preenchimento de valores para
@SortExpr
e@SortDir
.No que diz respeito ao SQL, mantenha o design e a formatação iguais entre os diferentes procedimentos armazenados, para que seja pelo menos puro e reconhecível quando você fizer alterações.
fonte
Essa abordagem evita que as colunas classificáveis sejam duplicadas duas vezes na ordem e é um IMO um pouco mais legível:
fonte
SQL dinâmico ainda é uma opção. Você só precisa decidir se essa opção é mais agradável do que a que você tem atualmente.
Aqui está um artigo que mostra que: http://www.4guysfromrolla.com/webtech/010704-1.shtml .
fonte
Meus aplicativos fazem muito isso, mas todos estão criando dinamicamente o SQL. No entanto, quando lido com procedimentos armazenados, faço o seguinte:
select * from dbo.fn_myData() where ... order by ...
para que você possa especificar dinamicamente a ordem de classificação lá.Então, pelo menos, a parte dinâmica está no seu aplicativo, mas o banco de dados ainda está realizando o trabalho pesado.
fonte
Uma técnica de procedimento armazenado (hack?) Que eu usei para evitar o SQL dinâmico para determinados trabalhos é ter uma coluna de classificação exclusiva. Ou seja,
Este é fácil de inserir na submissão - você pode concaturar campos na coluna mySort, reverter a ordem com funções matemáticas ou de data, etc.
De preferência, porém, eu uso minhas visualizações de grade do asp.net ou outros objetos com classificação interna para fazer a classificação depois que recuperar os dados do Sql-Server. Ou mesmo se não estiver embutido - por exemplo, tabelas de dados etc. no asp.net.
fonte
Existem algumas maneiras diferentes de você invadir isso.
Pré-requisitos:
Em seguida, insira em uma tabela temporária:
Método 2: configurar um servidor vinculado novamente e selecionar um deles usando o openquery: http://www.sommarskog.se/share_data.html#OPENQUERY
fonte
Pode haver uma terceira opção, já que o servidor possui muitos ciclos de reposição - use um procedimento auxiliar para fazer a classificação por meio de uma tabela temporária. Algo como
Advertência: Eu não testei isso, mas "deveria" funcionar no SQL Server 2005 (que criará uma tabela temporária a partir de um conjunto de resultados sem especificar as colunas antecipadamente).
fonte
Em algum momento, não vale a pena se afastar dos procedimentos armazenados e usar apenas consultas parametrizadas para evitar esse tipo de invasão?
fonte
Eu concordo, use o lado do cliente. Mas parece que essa não é a resposta que você deseja ouvir.
Então, é perfeito do jeito que é. Não sei por que você gostaria de alterá-lo ou até mesmo perguntar: "Existe uma maneira melhor". Realmente, deveria ser chamado "O Caminho". Além disso, parece funcionar bem e atender às necessidades do projeto e provavelmente será extensível o suficiente nos próximos anos. Como seus bancos de dados não são tributados e a classificação é realmente muito fácil , deve permanecer assim nos próximos anos.
Eu não suaria.
fonte
Quando você está paginando resultados classificados, o SQL dinâmico é uma boa opção. Se você é paranóico em relação à injeção de SQL, pode usar os números das colunas em vez do nome da coluna. Eu fiz isso antes de usar valores negativos para decrescente. Algo assim...
Então você só precisa garantir que o número esteja entre 1 e # de colunas. Você pode até expandir isso para uma lista de números de coluna e analisá-lo em uma tabela de ints usando uma função como essa . Então você criaria a ordem por cláusula assim ...
Uma desvantagem é que você deve se lembrar da ordem de cada coluna no lado do cliente. Especialmente quando você não exibe todas as colunas ou as exibe em uma ordem diferente. Quando o cliente deseja classificar, você mapeia os nomes das colunas para a ordem das colunas e gera a lista de entradas.
fonte
Um argumento contra a classificação no lado do cliente são os dados e a paginação de grandes volumes. Depois que a contagem de linhas ultrapassa o que você pode exibir facilmente, você geralmente classifica como parte de um salto / captura, que provavelmente você deseja executar no SQL.
Para o Entity Framework, você pode usar um procedimento armazenado para manipular sua pesquisa de texto. Se você encontrar o mesmo problema de classificação, a solução que eu vi é usar um processo armazenado para a pesquisa, retornando apenas uma chave de ID definida para a correspondência. Em seguida, consulte novamente (com a classificação) no banco de dados usando os IDs em uma lista (contém). A EF lida com isso muito bem, mesmo quando o conjunto de ID é bastante grande. Sim, são duas viagens de ida e volta, mas permite que você sempre mantenha sua classificação no banco de dados, o que pode ser importante em algumas situações e impede que você escreva uma grande quantidade de lógica no procedimento armazenado.
fonte
Que tal lidar com a classificação das coisas que exibem os resultados - grades, relatórios etc., e não no SQL?
EDITAR:
Para esclarecer as coisas, uma vez que esta resposta foi votada antes, elaborarei um pouco ...
Você declarou que sabia sobre a classificação do lado do cliente, mas queria evitar isso. Essa é sua decisão, é claro.
O que quero salientar, porém, é que, ao fazê-lo no lado do cliente, você é capaz de extrair dados UMA VEZ e depois trabalhar com eles da maneira que quiser - em vez de fazer várias viagens para o servidor a cada vez o tipo é alterado.
Seu SQL Server não está sendo tributado no momento e isso é incrível. Não deveria ser. Mas só porque ainda não está sobrecarregado, não significa que permanecerá assim para sempre.
Se você estiver usando qualquer um dos itens mais recentes do ASP.NET para exibição na web, muitos deles já estarão disponíveis.
Vale a pena adicionar tanto código a cada procedimento armazenado apenas para lidar com a classificação? Mais uma vez, sua ligação.
Eu não sou o único responsável por apoiá-lo. Mas pense um pouco no que será envolvido à medida que as colunas forem adicionadas / removidas nos vários conjuntos de dados usados pelos procedimentos armazenados (exigindo modificações nas instruções CASE) ou quando, de repente, em vez de classificar por duas colunas, o usuário decide que precisa de três - exigindo que você atualize todos os procedimentos armazenados que usam esse método.
Para mim, vale a pena obter uma solução funcional do lado do cliente e aplicá-la às poucas exibições de dados voltadas para o usuário e pronto. Se uma nova coluna for adicionada, ela já será tratada. Se o usuário quiser classificar por várias colunas, ele poderá classificar por duas ou vinte delas.
fonte
Desculpe, estou atrasado para a festa, mas aqui está outra opção para quem realmente deseja evitar o SQL dinâmico, mas quer a flexibilidade que ele oferece:
Em vez de gerar dinamicamente o SQL em tempo real, escreva o código para gerar um processo exclusivo para todas as variações possíveis. Em seguida, você pode escrever um método no código para examinar as opções de pesquisa e escolher o procedimento apropriado a ser chamado.
Se você tiver apenas algumas variações, poderá criar os procs manualmente. Mas se você tem muitas variações, em vez de precisar mantê-las todas, basta manter seu gerador de proc para que ele as recrie.
Como um benefício adicional, você obterá melhores planos SQL para obter um desempenho melhor dessa maneira.
fonte
Esta solução pode funcionar apenas no .NET, não sei.
Busco os dados no C # com a ordem de classificação inicial na cláusula SQL order by, coloco esses dados em um DataView, os coloco em cache em uma variável Session e os utilizo para criar uma página.
Quando o usuário clica no cabeçalho de uma coluna para classificar (ou página ou filtro), não volto ao banco de dados. Em vez disso, volto ao meu DataView em cache e defino sua propriedade "Sort" como uma expressão que construo dinamicamente, como faria com o SQL dinâmico. (Faço a filtragem da mesma maneira, usando a propriedade "RowFilter").
Você pode ver / sentir isso funcionando em uma demonstração do meu aplicativo, BugTracker.NET, em http://ifdefined.com/btnet/bugs.aspx
fonte
Você deve evitar a classificação do SQL Server, a menos que seja necessário. Por que não classificar no servidor de aplicativos ou no lado do cliente? O .NET Generics também faz uma classificação excepcional
fonte