Consulta paginada usando a classificação em diferentes colunas usando row_number () sobre () no SQL Server 2005

StackOverflow https://stackoverflow.com/questions/230058

  •  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?

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top