Query impaginata utilizzando l'ordinamento su colonne diverse utilizzando ROW_NUMBER () OVER () in SQL Server 2005
-
04-07-2019 - |
Domanda
Supponiamo che io stia utilizzando il database Northwind e vorrei eseguire una query tramite una procedura memorizzata che contiene, tra gli altri parametri, quanto segue:
-
@Offset
per indicare dove inizia l'impaginazione, -
@Limit
per indicare la dimensione della pagina, -
@SortColumn
per indicare la colonna utilizzata ai fini dell'ordinamento, -
@SortDirection
, per indicare l'ordinamento ascendente o discendente.
L'idea è di eseguire l'impaginazione sul database, poiché il set di risultati contiene migliaia di righe, pertanto la memorizzazione nella cache non è un'opzione (e l'utilizzo di VIEWSTATE non è nemmeno considerato come, IMO, fa schifo).
Come forse saprai, SQL Server 2005 fornisce la funzione ROW_NUMBER che restituisce il numero sequenziale di una riga all'interno di una partizione di un set di risultati, a partire da 1 per la prima riga in ciascuna partizione .
Abbiamo bisogno di ordinare su ogni colonna restituita (cinque in questo esempio) e SQL dinamico non è un'opzione, quindi abbiamo due possibilità: usare un sacco di IF ... ELSE ...
e avere 10 query, che è un inferno da mantenere, o con una query come la seguente:
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
Ho provato più volte la query, con diversi argomenti, e le sue prestazioni in realtà sono abbastanza buone, ma sembra comunque che potrebbe essere ottimizzato in altro modo.
C'è qualcosa di sbagliato in questa query o lo faresti in questo modo? Proponi un approccio diverso?
Soluzione
Semplice:
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
Ordinerà su NULL (DESC) se è selezionato l'ordine ASC o viceversa.
Consenti alla funzione ROW_NUMBER () di funzionare sulla stessa espressione ORDER BY.