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.