简体   繁体   中英

SQL Server : ORDER BY variable column CASE statement with NULL values

I've got a query that can spit out results sorted by a variety of columns, but I need to be able to handle null values in the sorted columns. Any help would be greatly appreciated. Here is an example:

Table [People]
Columns [Name], [Birthday] (NULLABLE)

Query where @Sort is an int designating which column to sort on, and @pageStart and @pageEnd are ints telling the query which results to return. (I am returning only a selection of the rows, so I am using a [RowNum] column nested in a CTE. There is also other processing happening, but I'm removing it for simplicity.):

;with results as(
SELECT [Name], [Birthday], ROW_NUMBER() OVER  (ORDER BY 
         CASE WHEN @Sort = 0 THEN [Name] END,
         CASE WHEN @Sort = 2 THEN [Birthday] END,
         CASE WHEN @Sort = 1 THEN [Name] END DESC,
         CASE WHEN @Sort = 3 THEN [Birthday] END DESC) AS RowNum
FROM [People]
)
SELECT [Name], [Birthday]
FROM results
WHERE RowNum BETWEEN @pageStart AND @pageEnd
--ORDER RowNum
--The last order by doesn't seem to be needed

I know that nulls can be handled with a statement such as:

ORDER BY (CASE WHEN [columnName] is NULL THEN 1 ELSE 0 END), [columnName]

I'm having a hard time applying that to the query I am working with... any help would be greatly appreciated! Let me know if I can clarify anything.

It seems like you're mostly there. Instead of a CASE...WHEN...END statement you could instead use ISNULL()

You'll need to choose relevant values from the same datatype, but for @Sort = 0 for example you could use

CASE WHEN @Sort = 0 THEN ISNULL([Name], '') END, 

It also looks like you could condense you sequence of CASE...WHEN...END statements into one more like

CASE @Sort
    WHEN 0 THEN ISNULL([Name], '')
    WHEN 2 THEN ISNULL([Birthday], 0)
...
...
END

You could combine the sort parameter with the null check in the CASE expressions:

;with results as(
SELECT [Name], [Birthday], ROW_NUMBER() OVER  (ORDER BY 
       CASE WHEN @Sort = 0 AND [Name] IS NULL THEN 1 ELSE 0 END, [Name],
       CASE WHEN @Sort = 2 AND [Birthday] IS NULL THEN 1 ELSE 0 END, [Birthday],
       CASE WHEN @Sort = 1 AND [Name] IS NULL THEN 1 ELSE 0 END, [Name] DESC,
       CASE WHEN @Sort = 3 AND [Birthday] IS NULL THEN 1 ELSE 0 END, [Birthday] DESC)As RowNum
FROM [People]
)
SELECT [Name], [Birthday]
FROM results
WHERE RowNum BETWEEN @pageStart AND @pageEnd

Your query is good in terms of code re-use. Unfortunately it's also poor in terms of optimisability. (Is that even a real word?)

For this single query, the optimiser needs to build a single execution plan. It can't, for example, switch between different indexes for the ordering. Instead it will have to 'manually' re-order the data as your parameters change.

This is especially relevant in paging. With a suitable index, the optimiser can use a range seek to jump straight to the rows you need. In the case of your single query, the effect will be to actually process the whole table, ordering appropriately, then jump to the appropriate records. (The result of having a single plan to suit all parameters.) For any significant amount of data, that is an extermely large overhead.

For this reason, it's actually often much more performant to approach this with Dynamic SQL. This allows each different ordering to have it's own execution plan. And then each plan case use the index most appropriate to those needs.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM