Consulta paginada utilizando ordenación en diferentes columnas usando ROW_NUMBER () OVER () en SQL Server 2005
-
04-07-2019 - |
Pregunta
Supongamos que estoy usando la base de datos Northwind y me gustaría ejecutar una consulta a través de un procedimiento almacenado que contiene, entre otros parámetros, lo siguiente:
-
@Offset
para indicar dónde comienza la paginación, -
@Limit
para indicar el tamaño de la página, -
@SortColumn
para indicar la columna utilizada para propósitos de clasificación, -
@SortDirection
, para indicar la clasificación ascendente o descendiente.
La idea es hacer la paginación en la base de datos, ya que el conjunto de resultados contiene miles de filas, por lo que el almacenamiento en caché no es una opción (y el uso de VIEWSTATE ni siquiera se considera, IMO, apesta).
Como usted sabe, SQL Server 2005 proporciona la función ROW_NUMBER que devuelve el número secuencial de una fila dentro de una partición de un conjunto de resultados, comenzando en 1 para la primera fila en cada partición .
Necesitamos clasificar en cada columna devuelta (cinco en este ejemplo) y SQL dinámico no es una opción, por lo que tenemos dos posibilidades: usar un montón de IF ... ELSE ...
y tener 10 consultas, lo que es un infierno de mantener, o tener una consulta como la siguiente:
WITH PaginatedOrders AS (
SELECT
CASE (@SortColumn + ':' + @SortDirection)
WHEN 'OrderID:A' THEN ROW_NUMBER() OVER (ORDER BY Orders.OrderID ASC)
WHEN 'OrderID:D' THEN ROW_NUMBER() OVER (ORDER BY Orders.OrderID DESC)
WHEN 'CustomerID:A' THEN ROW_NUMBER() OVER (ORDER BY Orders.CustomerID ASC)
WHEN 'CustomerID:D' THEN ROW_NUMBER() OVER (ORDER BY Orders.CustomerID DESC)
WHEN 'EmployeeID:A' THEN ROW_NUMBER() OVER (ORDER BY Orders.EmployeeID ASC)
WHEN 'EmployeeID:D' THEN ROW_NUMBER() OVER (ORDER BY Orders.EmployeeID DESC)
WHEN 'OrderDate:A' THEN ROW_NUMBER() OVER (ORDER BY Orders.OrderDate ASC)
WHEN 'OrderDate:D' THEN ROW_NUMBER() OVER (ORDER BY Orders.OrderDate DESC)
WHEN 'ShippedDate:A' THEN ROW_NUMBER() OVER (ORDER BY Orders.OrderID ASC)
WHEN 'ShippedDate:D' THEN ROW_NUMBER() OVER (ORDER BY Orders.OrderID DESC)
END AS RowNumber,
OrderID, CustomerID, EmployeeID, OrderDate, ShippedDate
FROM Orders
-- WHERE clause goes here
)
SELECT
RowNumber, OrderID, CustomerID, EmployeeID, OrderDate, ShippedDate,
@Offset, @Limit, @SortColumn, @SortDirection
FROM PaginatedOrders
WHERE RowNumber BETWEEN @Offset AND (@Offset + @Limit - 1)
ORDER BY RowNumber
He intentado la consulta varias veces, con diferentes argumentos, y su rendimiento es bastante bueno en realidad, pero todavía parece que podría optimizarse de otra manera.
¿Hay algún problema con esta consulta o lo harías de esta manera? ¿Propones un enfoque diferente?
Solución
Simple:
SELECT
OrderID, CustomerID, EmployeeID, OrderDate, ShippedDate,
@Offset, @Limit, @SortColumn, @SortDirection
FROM
Orders
WHERE
ROW_NUMBER() OVER
(
ORDER BY
/* same expression as in the ORDER BY of the whole query */
) BETWEEN (@PageNum - 1) * @PageSize + 1 AND @PageNum * @PageSize
/* AND more conditions ... */
ORDER BY
CASE WHEN @SortDirection = 'A' THEN
CASE @SortColumn
WHEN 'OrderID' THEN OrderID
WHEN 'CustomerID' THEN CustomerID
/* more... */
END
END,
CASE WHEN @SortDirection = 'D' THEN
CASE @SortColumn
WHEN 'OrderID' THEN OrderID
WHEN 'CustomerID' THEN CustomerID
/* more... */
END
END DESC
Esto se ordenará en NULL (DESC) si se selecciona el orden ASC, o viceversa.
Deje que la función ROW_NUMBER () funcione sobre la misma expresión ORDER BY.