Dynamic ORDER BY
By Garth Wells
on 22 January 2001
| 80 Comments
| Tags: ORDER BY
We've got another article chock full of SQL goodness from Garth. This one covers creating a dynamic ORDER BY clause for your SELECT statements. It's a follow up to his earlier article on dynamic WHERE clauses.
In my previous article I demonstrated how to use the COALESCE function to create a dynamic WHERE clause. In this article, which is based on solutions posted in the public newsgroup forums, I'll show you how to use the CASE function to implement a dynamic ORDER BY. In the end, I'll use both techniques together to create a stored procedure that supports both a dynamic WHERE and ORDER BY.
The ORDER BY Clause
SQL Server retrieves data in random order. Some developers errantly think that when a simple SELECT (as shown below) is executed the data is retrieved in the order in which it was entered.
USE Northwind
go
SELECT CompanyName,
ContactName,
ContactTitle
FROM Customers
Although it may be returned in the order in which it was entered, the only way to ensure order is with the ORDER BY clause. The following shows how to sort the resultset on company name in descending order.
SELECT CompanyName,
ContactName,
ContactTitle
FROM Customers
ORDER BY CompanyName DESC
The CASE Statement
The CASE statement is used to add conditional logic to Transact-SQL statements. The statements listed below show to use CASE to SELECT a message that is reflective of the time of day.
DECLARE @DateTime datetime
SET @DateTime = '13:00:01'
SELECT @DateTime
SELECT CASE WHEN DATEPART(hour,@DateTime) < 12 THEN 'Good Morning'
WHEN DATEPART(hour,@DateTime) >= 13
AND DATEPART(hour,@DateTime) < 18 THEN 'Good Afternoon'
ELSE 'Good Evening'
END
--Results--
Good Afternoon
Notice that I did not include the date portion when setting the value for the @DateTime variable. When only the time portion is provided, the date is set to 01/01/1900. The CASE statement uses the DATEPART function to determine the hour and then returns the appropriate greeting.
Dynamic ORDER BY
Now that we've covered the basics, let's see how CASE can be used to implement a dynamic ORDER BY. Assume you want to sort the results of the Customers query on any of the columns referenced in the SELECT. The following statements show how this is done.
DECLARE @SortOrder tinyint
SET @SortOrder = 2
SELECT CompanyName,
ContactName,
ContactTitle
FROM Customers
ORDER BY CASE WHEN @SortOrder = 1 THEN CompanyName
WHEN @SortOrder = 2 THEN ContactName
ELSE ContactTitle
END
A variable is used to specify the desired sort and CASE resolves the value to the appropriate column name. This approach is obviously best suited for a stored procedure, so let's create one with the following.
CREATE PROCEDURE ps_Customers_SELECT_DynamicOrderBy
@SortOrder tinyint = NULL
AS
SELECT CompanyName,
ContactName,
ContactTitle
FROM Customers
ORDER BY CASE WHEN @SortOrder = 1 THEN CompanyName
WHEN @SortOrder = 2 THEN ContactName
ELSE ContactTitle
END
You can then call the procedure with any of the statements listed below.
EXEC ps_Customers_SELECT_DynamicOrderBy
EXEC ps_Customers_SELECT_DynamicOrderBy 1
EXEC ps_Customers_SELECT_DynamicOrderBy 2
Dynamic WHERE + ORDER BY
In one of the examples I included in my book, Code Centric: T-SQL Programming with Stored Procedures and Triggers, I show how to use both COALESCE and CASE to create a stored procedure that supports both a dynamic WHERE and ORDER BY. You can see the final results of the example by going here: www.SQLBook.com/SearchScreen.asp
The search screen allows you to specify the sort to order the data and one or more parameters to filter it. You will also notice that the number of rows returned are shown on the page. The procedure that retrieves the data is shown here.
CREATE PROCEDURE ps_Player_SELECT_ByStats (
@OrderBy tinyint = 1,
@Pla_BattingAverage smallint = NULL,
@Pla_HomeRuns smallint = NULL,
@Pla_RBIs smallint = NULL,
@Pla_StolenBases smallint = NULL,
@RowsReturned smallint = NULL OUTPUT )
AS
SET NOCOUNT ON
SELECT Pla_FName+' '+Pla_LName AS Name,
Pla_BattingAverage,
Pla_HomeRuns,
Pla_RBIs,
Pla_StolenBases
FROM Players
WHERE Pla_BattingAverage >= COALESCE(@Pla_BattingAverage,0) AND
Pla_HomeRuns >= COALESCE(@Pla_HomeRuns,0) AND
Pla_RBIs >= COALESCE(@Pla_RBIs,0) AND
Pla_StolenBases >= COALESCE(@Pla_StolenBases,0)
ORDER BY CASE WHEN @OrderBY = 1 THEN Pla_BattingAverage
WHEN @OrderBY = 2 THEN Pla_HomeRuns
WHEN @OrderBY = 3 THEN Pla_RBIs
WHEN @OrderBY = 4 THEN Pla_StolenBases
END DESC
SET @RowsReturned = @@ROWCOUNT
The use of COALESCE is slightly different than in the previous article. I used zero instead of the column names because it is a little easier to understand the logic. None of the columns will contain NULLs or negative values, so they will always be greater than or equal to zero. This ensures the comparison will evaluate to true when a parameter value is not supplied.
The search screen (SearchScreen.asp) portion of the example is what I call "completing the example." Quite a few of the examples in my book are "completed" by showing you how to access the SQL Statements via a GUI created with ASP. My research indicated that developers not only want to see how to create SQL statements within Query Analyzer, but also want to see how they are accessed with a programming language used to create a front-end application. If you like to work through these type of examples, I am sure you will enjoy my book.