Consulta paginada usando a classificação em diferentes colunas usando row_number () sobre () no SQL Server 2005
-
04-07-2019 - |
Pergunta
Vamos supor que estou usando o banco de dados Northwind e gostaria de executar uma consulta por meio de um procedimento armazenado que contém, entre outros parâmetros, o seguinte:
@Offset
para indicar onde a paginação começa,@Limit
para indicar o tamanho da página,@SortColumn
para indicar a coluna usada para fins de classificação,@SortDirection
, para indicar uma classificação ascendente ou descendente.
A idéia é fazer a paginação no banco de dados, pois o conjunto de resultados contém milhares de linhas, para que o cache não seja uma opção (e o uso do ViewState nem é considerado como, IMO, é uma merda).
Como você deve saber o SQL Server 2005 fornece a função Row_number que Retorna o número seqüencial de uma linha dentro de uma partição de um conjunto de resultados, começando em 1 para a primeira linha em cada partição.
Precisamos classificar em cada coluna retornada (cinco neste exemplo) e o SQL dinâmico não é uma opção, por isso temos duas possibilidades: usando muitas IF ... ELSE ...
e ter 10 consultas, o que é um inferno para manter, ou ter uma consulta como a seguinte:
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
Eu tentei a consulta várias vezes, com argumentos diferentes, e seu desempenho é muito bom, mas parece que parece que pode ser otimizado de outra maneira.
Há algo de errado com esta consulta ou você faria dessa maneira? Você propõe uma abordagem diferente?
Solução
Simples:
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
Isso classificará o NULL (DEC) se a ordem ASC for selecionada ou vice -versa.
Deixe a função row_number () funcionar na mesma ordem por expressão.