Requête paginée utilisant le tri sur différentes colonnes à l'aide de ROW_NUMBER () OVER () dans SQL Server 2005
-
04-07-2019 - |
Question
Supposons que j'utilise la base de données Northwind et que je souhaite exécuter une requête via une procédure stockée contenant, entre autres paramètres, les éléments suivants:
-
@Offset
pour indiquer le début de la pagination, -
@Limit
pour indiquer la taille de la page, -
@SortColumn
pour indiquer la colonne utilisée pour le tri, -
@SortDirection
, pour indiquer le tri ascendant ou descendant.
L’idée est de faire la pagination sur la base de données, car le jeu de résultats contient des milliers de lignes. La mise en cache n’est donc pas une option (et utiliser VIEWSTATE n’est même pas considéré comme, IMO, c'est nul).
Comme vous le savez peut-être, SQL Server 2005 fournit la fonction ROW_NUMBER . renvoie le numéro séquentiel d'une ligne dans une partition d'un jeu de résultats, en commençant à 1 pour la première ligne de chaque partition .
Nous avons besoin de trier chaque colonne renvoyée (cinq dans cet exemple) et le SQL dynamique n’est pas une option. Nous avons donc deux possibilités: utiliser beaucoup de IF ... ELSE ...
et avoir 10 requêtes, ce qui est un enfer à maintenir, ou ayant une requête comme celle-ci:
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
J'ai essayé la requête plusieurs fois, avec des arguments différents, et ses performances sont plutôt bonnes, mais il semble toujours qu'elle pourrait être optimisée d'une autre manière.
Quelque chose ne va pas avec cette requête ou vous le feriez de cette façon? Proposez-vous une approche différente?
La solution
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
Ceci triera sur NULL (DESC) si la commande ASC est sélectionnée, ou inversement.
Laissez la fonction ROW_NUMBER () travailler sur la même expression ORDER BY.