Разбитый на страницы запрос с использованием сортировки по разным столбцам с использованием ROW_NUMBER() ВМЕСТО () в SQL Server 2005
-
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() работает в том же порядке по выражению.