Разбитый на страницы запрос с использованием сортировки по разным столбцам с использованием ROW_NUMBER() ВМЕСТО () в SQL Server 2005

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

  •  04-07-2019
  •  | 
  •  

Вопрос

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

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

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

Как вы, возможно, знаете, SQL Server 2005 предоставляет эту функцию НОМЕР СТРОКИ который возвращает порядковый номер строки в разделе результирующего набора, начинающийся с 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

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

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

Это было полезно?

Решение

Простой:

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() работает в том же порядке по выражению.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top