Разбитый на страницы запрос, использующий сортировку по различным столбцам с помощью ROW_NUMBER () OVER () в SQL Server 2005 - PullRequest
7 голосов
/ 23 октября 2008

Предположим, я использую базу данных Northwind и хочу выполнить запрос с помощью хранимой процедуры, которая содержит, помимо прочих параметров, следующее:

  • @Offset, чтобы указать, где начинается нумерация страниц,
  • @Limit для указания размера страницы,
  • @SortColumn для указания столбца, используемого для сортировки,
  • @SortDirection, чтобы указать сортировку по возрастанию или по убыванию.

Идея состоит в том, чтобы сделать нумерацию страниц в базе данных, поскольку результирующий набор содержит тысячи строк, поэтому кэширование не является опцией (а использование VIEWSTATE даже не рассматривается как, IMO, отстой).

Как вы, возможно, знаете, SQL Server 2005 предоставляет функцию ROW_NUMBER , которая возвращает порядковый номер строки в разделе набора результатов, начиная с 1 для первой строки в каждом разделе .

Нам нужна сортировка по каждому возвращаемому столбцу (пять в этом примере), и динамический SQL не является вариантом, поэтому у нас есть две возможности: использовать множество IF ... ELSE ... и иметь 10 запросов, что является адским обслуживанием, или иметь запрос, подобный следующему:

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

Я пробовал запрос несколько раз, с разными аргументами, и его производительность на самом деле довольно хорошая, но он все равно выглядит так, как будто его можно оптимизировать другим способом.

Что-то не так с этим запросом, или вы бы сделали это так? Вы предлагаете другой подход?

1 Ответ

6 голосов
/ 23 октября 2008

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

Это будет сортировать по NULL (DESC), если выбран порядок ASC, или наоборот.

Пусть функция ROW_NUMBER () работает с одним и тем же выражением ORDER BY.

...