Este es un problema que he pasado horas investigando en el pasado. Me parece algo que debería haber sido abordado por las soluciones RDBMS modernas , pero hasta ahora no he encontrado nada que realmente aborde lo que veo como una necesidad increíblemente común en cualquier aplicación web o Windows con una base de datos.
Hablo de ordenación dinámica. En mi mundo de fantasía, debería ser tan simple como algo como:
ORDER BY @sortCol1, @sortCol2
Este es el ejemplo canónico dado por los novatos desarrolladores de SQL y procedimientos almacenados en todos los foros de Internet. "¿Por qué no es esto posible?" ellos preguntan. Invariablemente, eventualmente alguien viene a darles una conferencia sobre la naturaleza compilada de los procedimientos almacenados, de los planes de ejecución en general, y todo tipo de otras razones por las cuales no es posible poner un parámetro directamente en una ORDER BY
cláusula.
Sé lo que algunos de ustedes ya están pensando: "Deje que el cliente haga la clasificación, entonces". Naturalmente, esto descarga el trabajo de su base de datos. Sin embargo, en nuestro caso, nuestros servidores de bases de datos ni siquiera están sudando el 99% del tiempo y ni siquiera son multinúcleo ni ninguna de las otras miles de mejoras en la arquitectura del sistema que ocurren cada 6 meses. Solo por esta razón, hacer que nuestras bases de datos manejen la clasificación no sería un problema. Además, las bases de datos son muybueno en la clasificación Están optimizados para ello y han tenido años para hacerlo bien, el lenguaje para hacerlo es increíblemente flexible, intuitivo y simple y, sobre todo, cualquier escritor principiante de SQL sabe cómo hacerlo y, lo que es más importante, saben cómo editarlo, realizar cambios, realizar tareas de mantenimiento, etc. Cuando sus bases de datos están lejos de estar sujetas a impuestos y solo desea simplificar (¡y acortar!) el tiempo de desarrollo, esto parece una opción obvia.
Luego está el problema web. He jugado con JavaScript que hará la clasificación del lado del cliente de las tablas HTML, pero inevitablemente no son lo suficientemente flexibles para mis necesidades y, una vez más, dado que mis bases de datos no están excesivamente gravadas y pueden hacer la clasificación de manera muy fácil, me resulta difícil justificar el tiempo que llevaría volver a escribir o clasificar mi propio clasificador de JavaScript. Lo mismo ocurre generalmente con la ordenación del lado del servidor, aunque probablemente ya sea mucho más preferido que JavaScript. No soy uno que le guste particularmente los gastos generales de los DataSets, así que demándame.
Pero esto trae de vuelta el punto de que no es posible, o más bien, no es fácil. He hecho, con sistemas anteriores, una forma increíblemente hack de obtener una clasificación dinámica. No era bonito, ni intuitivo, simple o flexible, y un escritor principiante de SQL se perdería en segundos. Ya parece que esto no es tanto una "solución" sino una "complicación".
Los siguientes ejemplos no están destinados a exponer ningún tipo de mejores prácticas o buen estilo de codificación ni nada, ni son indicativos de mis habilidades como programador de T-SQL. Son lo que son y admito que son confusos, de mala forma y simplemente piratas.
Pasamos un valor entero como parámetro a un procedimiento almacenado (llamemos al parámetro simplemente "ordenar") y a partir de eso determinamos un montón de otras variables. Por ejemplo ... digamos que sort es 1 (o el valor predeterminado):
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
Ya puede ver cómo si declarara más variables @colX para definir otras columnas, realmente podría ser creativo con las columnas para ordenar en función del valor de "ordenar" ... para usarlo, generalmente termina pareciéndose a lo siguiente cláusula increíblemente desordenada:
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 es un ejemplo muy despojado. Lo real, ya que generalmente tenemos cuatro o cinco columnas para admitir la clasificación, cada una con una posible secundaria o incluso una tercera columna para clasificar además de eso (por ejemplo, fecha descendente y luego ordenada secundariamente por nombre ascendente) y cada una clasificación direccional que efectivamente duplica el número de casos. Sí ... se pone peludo muy rápido.
La idea es que uno podría "cambiar fácilmente" los casos de clasificación de manera que el vehículo se clasifique antes del tiempo de almacenamiento ... pero la pseudo-flexibilidad, al menos en este simple ejemplo, realmente termina allí. Esencialmente, cada caso que falla una prueba (porque nuestro método de clasificación no se aplica esta vez) presenta un valor NULL. Y así terminas con una cláusula que funciona como la siguiente:
ORDER BY NULL DESC, NULL, [storagedatetime] DESC, blah blah
Tienes la idea. Funciona porque SQL Server ignora efectivamente los valores nulos en orden por cláusulas. Esto es increíblemente difícil de mantener, como puede ver cualquier persona con conocimientos básicos de SQL. Si los he perdido, no se sienta mal. Nos llevó mucho tiempo hacerlo funcionar y todavía nos confundimos al intentar editarlo o crear otros nuevos como este. Afortunadamente, no es necesario cambiarlo con frecuencia, de lo contrario se convertiría rápidamente en "no vale la pena".
Sin embargo , funcionó.
Mi pregunta es entonces: ¿hay una mejor manera?
Estoy de acuerdo con otras soluciones que no sean las de Procedimiento almacenado, ya que me doy cuenta de que puede que no sea el camino a seguir. Preferiblemente, me gustaría saber si alguien puede hacerlo mejor dentro del Procedimiento almacenado, pero si no es así, ¿cómo se maneja permitiendo que el usuario ordene dinámicamente tablas de datos (también bidireccionalmente) con ASP.NET?
¡Y gracias por leer (o al menos descremar) una pregunta tan larga!
PD: Alégrate de no haber mostrado mi ejemplo de un procedimiento almacenado que admite la ordenación dinámica, el filtrado dinámico / la búsqueda de texto de columnas, la paginación a través de ROWNUMBER () OVER, y prueba ... atrapar con la reversión de transacciones en errores ... "tamaño gigante" ni siquiera comienza a describirlos.
Actualizar:
- Me gustaría evitar el SQL dinámico . Analizar una cadena y ejecutar un EXEC en ella anula mucho el propósito de tener un procedimiento almacenado en primer lugar. Sin embargo, a veces me pregunto si los inconvenientes de hacer algo así no valdrían la pena, al menos en estos casos especiales de clasificación dinámica. Aún así, siempre me siento sucio cada vez que hago cadenas SQL dinámicas como esa, como si aún viviera en el mundo ASP clásico.
- Gran parte de la razón por la que queremos procedimientos almacenados en primer lugar es por seguridad . No puedo hacer una llamada por cuestiones de seguridad, solo sugerir soluciones. Con SQL Server 2005 podemos establecer permisos (por usuario si es necesario) a nivel de esquema en procedimientos almacenados individuales y luego denegar cualquier consulta directamente en las tablas. Criticar los pros y los contras de este enfoque es quizás para otra pregunta, pero nuevamente no es mi decisión. Solo soy el mono código principal. :)
fuente
Respuestas:
Sí, es un dolor, y la forma en que lo haces se parece a lo que hago:
Esto, para mí, sigue siendo mucho mejor que construir SQL dinámico a partir del código, lo que se convierte en una pesadilla de escalabilidad y mantenimiento para los DBA.
Lo que hago desde el código es refactorizar la paginación y la clasificación, por lo que al menos no tengo mucha repetición allí con los valores de relleno para
@SortExpr
y@SortDir
.En lo que respecta al SQL, mantenga el diseño y el formato iguales entre los diferentes procedimientos almacenados, para que al menos sea ordenado y reconocible cuando realice cambios.
fuente
Este enfoque evita que las columnas ordenables se dupliquen dos veces en el orden, y es un poco más legible en mi opinión:
fuente
SQL dinámico sigue siendo una opción. Solo tiene que decidir si esa opción es más sabrosa que la que tiene actualmente.
Aquí hay un artículo que muestra que: http://www.4guysfromrolla.com/webtech/010704-1.shtml .
fuente
Mis aplicaciones hacen esto mucho, pero todas están construyendo dinámicamente el SQL. Sin embargo, cuando trato con procedimientos almacenados hago esto:
select * from dbo.fn_myData() where ... order by ...
para que pueda especificar dinámicamente el orden de clasificación allí.Entonces, al menos, la parte dinámica está en su aplicación, pero la base de datos todavía está haciendo el trabajo pesado.
fuente
Una técnica de procedimiento almacenado (¿pirateo?) Que he usado para evitar SQL dinámico para ciertos trabajos es tener una columna de clasificación única. Es decir,
Este es fácil de superar en el envío: puede concatenar campos en su columna mySort, invertir el orden con funciones matemáticas o de fecha, etc.
Sin embargo, preferiblemente, uso mis vistas de cuadrícula asp.net u otros objetos con clasificación integrada para hacer la clasificación por mí DESPUÉS de recuperar los datos de Sql-Server. O incluso si no está integrado, por ejemplo, tablas de datos, etc. en asp.net.
fuente
Hay un par de formas diferentes en que puedes hackear esto.
Prerrequisitos:
Luego inserte en una tabla temporal:
Método # 2: configure un servidor vinculado de nuevo a sí mismo, luego seleccione de esto usando openquery: http://www.sommarskog.se/share_data.html#OPENQUERY
fuente
Puede haber una tercera opción, ya que su servidor tiene muchos ciclos de repuesto: use un procedimiento auxiliar para ordenar a través de una tabla temporal. Algo como
Advertencia: no he probado esto, pero "debería" funcionar en SQL Server 2005 (que creará una tabla temporal a partir de un conjunto de resultados sin especificar las columnas por adelantado).
fuente
En algún momento, ¿no vale la pena alejarse de los procedimientos almacenados y simplemente usar consultas parametrizadas para evitar este tipo de piratería?
fuente
Estoy de acuerdo, use el lado del cliente. Pero parece que esa no es la respuesta que desea escuchar.
Entonces, es perfecto tal como es. No sé por qué querrías cambiarlo, o incluso preguntar "¿Hay una mejor manera?" Realmente, debería llamarse "The Way". Además, parece funcionar bien y se adapta bien a las necesidades del proyecto y probablemente será lo suficientemente extensible en los próximos años. Dado que sus bases de datos no están sujetas a impuestos y la clasificación es realmente muy fácil , debería permanecer así en los próximos años.
No lo sudaría.
fuente
Cuando busca resultados ordenados, el SQL dinámico es una buena opción. Si eres paranoico acerca de la inyección SQL, puedes usar los números de columna en lugar del nombre de la columna. He hecho esto antes de usar valores negativos para descender. Algo como esto...
Entonces solo necesita asegurarse de que el número esté dentro de 1 a # de columnas. Incluso podría expandir esto a una lista de números de columna y analizarlo en una tabla de entradas usando una función como esta . Entonces construirías el orden por cláusula así ...
Un inconveniente es que debe recordar el orden de cada columna en el lado del cliente. Especialmente, cuando no muestra todas las columnas o las muestra en un orden diferente. Cuando el cliente desea ordenar, asigna los nombres de las columnas al orden de las columnas y genera la lista de entradas.
fuente
Un argumento en contra de hacer la clasificación en el lado del cliente son los datos de gran volumen y la paginación. Una vez que el recuento de filas supera lo que puede mostrar fácilmente, a menudo está ordenando como parte de un salto / toma, que probablemente quiera ejecutar en SQL.
Para Entity Framework, puede usar un procedimiento almacenado para manejar su búsqueda de texto. Si encuentra el mismo problema de clasificación, la solución que he visto es utilizar un proceso almacenado para la búsqueda, devolviendo solo una clave de identificación establecida para la coincidencia. A continuación, vuelva a consultar (con el tipo) contra el db utilizando los identificadores en una lista (contiene). EF maneja esto bastante bien, incluso cuando el conjunto de ID es bastante grande. Sí, se trata de dos viajes de ida y vuelta, pero le permite mantener siempre su clasificación en la base de datos, lo que puede ser importante en algunas situaciones, y le impide escribir un montón de lógica en el procedimiento almacenado.
fuente
¿Qué tal manejar la ordenación en las cosas que muestran los resultados: cuadrículas, informes, etc. en lugar de en SQL?
EDITAR:
Para aclarar las cosas ya que esta respuesta fue rechazada anteriormente, explicaré un poco ...
Dijiste que sabías sobre la clasificación del lado del cliente pero que querías evitarlo. Esa es tu decisión, por supuesto.
Sin embargo, lo que quiero señalar es que al hacerlo en el lado del cliente, puede extraer los datos UNA VEZ y luego trabajar con ellos como desee, en lugar de hacer múltiples viajes de ida y vuelta al servidor cada vez El tipo se cambia.
Su SQL Server no está siendo gravado en este momento y eso es increíble. No debe ser Pero solo porque no esté sobrecargado aún no significa que se quedará así para siempre.
Si está utilizando cualquiera de los elementos más nuevos de ASP.NET para mostrar en la web, muchos de esos elementos ya están integrados.
¿Vale la pena agregar tanto código a cada procedimiento almacenado solo para manejar la clasificación? De nuevo, tu llamada.
No soy yo quien finalmente se encargará de apoyarlo. Pero piense en lo que implicará a medida que se agregan / eliminan columnas dentro de los diversos conjuntos de datos utilizados por los procedimientos almacenados (que requieren modificaciones a las declaraciones CASE) o cuando de repente, en lugar de ordenar por dos columnas, el usuario decide que necesita tres: requiriendo que actualice ahora cada uno de sus procedimientos almacenados que utiliza este método.
Para mí, vale la pena obtener una solución del lado del cliente que funcione y aplicarla a un puñado de pantallas de datos orientadas al usuario y terminar con eso. Si se agrega una nueva columna, ya está manejada. Si el usuario desea ordenar por múltiples columnas, puede ordenar por dos o veinte de ellas.
fuente
Lo siento, llego tarde a la fiesta, pero aquí hay otra opción para aquellos que realmente quieren evitar el SQL dinámico, pero quieren la flexibilidad que ofrece:
En lugar de generar dinámicamente el SQL sobre la marcha, escriba código para generar un proceso único para cada variación posible. Luego puede escribir un método en el código para ver las opciones de búsqueda y hacer que elija el proceso apropiado para llamar.
Si solo tiene algunas variaciones, puede crear los procedimientos a mano. Pero si tiene muchas variaciones, en lugar de tener que mantenerlas todas, simplemente mantendrá su generador de procesos para que las vuelva a crear.
Como beneficio adicional, obtendrá mejores planes de SQL para un mejor rendimiento al hacerlo también de esta manera.
fuente
Es posible que esta solución solo funcione en .NET, no lo sé.
Busco los datos en C # con el orden de clasificación inicial en el orden de SQL por cláusula, pongo esos datos en un DataView, los almacena en caché en una variable de sesión y los uso para construir una página.
Cuando el usuario hace clic en el encabezado de una columna para ordenar (o página, o filtro), no vuelvo a la base de datos. En cambio, vuelvo a mi DataView en caché y establezco su propiedad "Ordenar" en una expresión que construyo dinámicamente, al igual que lo haría con SQL dinámico. (Realizo el filtrado de la misma manera, usando la propiedad "RowFilter").
Puede verlo / sentirlo funcionando en una demostración de mi aplicación, BugTracker.NET, en http://ifdefined.com/btnet/bugs.aspx
fuente
Debe evitar la ordenación de SQL Server, a menos que sea necesario. ¿Por qué no ordenar en el servidor de aplicaciones o en el lado del cliente? También .NET Generics hace una clasificación excepcional
fuente