Reputation: 381
I'm using a stored procedure to try to get the first few rows from a sorted SQL query where the input paramaters specifiy the amount of rows to retrieve and the sorting criteria.
I've been able to properly retrieve the first few rows however when I try to sort the results, they simply appear sorted using the default criteria (Primary key).
So I was wondering if anyone could take a look at it?
USE [database]
GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROCEDURE [dbo].[x0]
@username nvarchar(50),
@sortBy varchar(50),
@sortDirection varchar(4),
@startRow int,
@endRow int
AS
With Ordering AS(
SELECT ROW_NUMBER() OVER (Order by
CASE WHEN @sortBy='datecreate' THEN 'datecreate'
WHEN @sortBy='id' THEN 'id'
WHEN @sortBy='DisplayName' THEN 'DisplayName'
END,
CASE WHEN @sortDirection='asc' THEN 'asc'
WHEN @sortDirection='desc' THEN 'desc'
END) AS RowNumber,
dbo.x1.*, dbo.x2.*
FROM dbo.x1 INNER JOIN dbo.x2 ON dbo.x1.type = dbo.x2.type
where username = @username
)
SELECT RowNumber, *, DisplayName AS DisplayName
FROM Ordering
Where RowNumber BETWEEN @startRow AND @endRow
I've also tried moving the sorting criteria to the outer SQL query (Where RowNumber BETWEEN @startRow AND @endRow) without much luck.
Upvotes: 0
Views: 827
Reputation: 1269443
Your problem is the use of constants in the query. You are sorting by constant strings, rather than columns. I think you are mixing up dynamic SQL and regular SQL.
To get the names, you would want to use:
SELECT ROW_NUMBER() OVER (Order by
CASE WHEN @sortBy='datecreate' THEN datecreate
WHEN @sortBy='id' THEN id
WHEN @sortBy='DisplayName' THEN DisplayName
END,
However, this won't work, because of the type conflict. You have two options. First, either convert all the types to string with the right sort order (YYYY-MM-DD for dates, zero pads for numbers). Or, since this is a stored procedure, create a separate query for each one.
The issue is that you might get everything as a string, but I'm still not sure how to get the ASC and DESC in. I think you will need two queries for this, which is feasible since this is inside a stored procedure.
I like Aaron's answer, but I think it can be improved.
WITH Ordering AS (
SELECT ROW_NUMBER() OVER
(ORDER BY (case when @sortBy = 'datecreate' and @sortdirection = 'ASC'
then datecreate
end),
(case when @sortBy = 'datecreate' and @sortdirection = 'DESC'
then datecreate
end) desc,
(case when @sortBy = 'id' and @sortdirection = 'ASC'
then id
end),
(case when @sortBy = 'id' and @sortdirection = 'DESC'
then id
end) desc,
(case when @sortBy = 'DisplayName' and @sortdirection = 'ASC'
then DisplayName
end),
(case when @sortBy = 'DisplayName' and @sortdirection = 'DESC'
then DisplayName
end) desc
) as rn,
dbo.x1.*, dbo.x2.*
FROM dbo.x1 INNER JOIN dbo.x2 ON dbo.x1.type = dbo.x2.type
where username = @username
)
And so on. The difference is that there is only one row_number() call, with a more complicated ordering expression. All the values will be NULL except for the one actually being used for the sort.
Upvotes: 2
Reputation: 280252
You could do it this way:
;WITH Ordering AS
(
SELECT rnd = ROW_NUMBER() OVER (ORDER BY datecreate),
rni = ROW_NUMBER() OVER (ORDER BY id),
rnn = ROW_NUMBER() OVER (ORDER BY DisplayName),
rndd = ROW_NUMBER() OVER (ORDER BY datecreate DESC),
rnid = ROW_NUMBER() OVER (ORDER BY id DESC),
rnnd = ROW_NUMBER() OVER (ORDER BY DisplayName DESC),
dbo.x1.*, dbo.x2.*
FROM dbo.x1 INNER JOIN dbo.x2 ON dbo.x1.type = dbo.x2.type
where username = @username
),
x AS
(
SELECT RowNumber = CASE @SortDirection
WHEN 'asc' THEN CASE @SortBy
WHEN 'datecreate' THEN rnd
WHEN 'id' THEN rni
WHEN 'DisplayName' THEN rnn
END
WHEN 'desc' THEN CASE @sortBy
WHEN 'datecreate' THEN rndd
WHEN 'id' THEN rnid
WHEN 'DisplayName' THEN rnnd
END, *, DisplayName AS DisplayName
FROM Ordering
)
SELECT * FROM x
WHERE RowNumber BETWEEN @startRow AND @endRow
ORDER BY RowNumber;
But quite honestly I think you'll get better performance from dynamic SQL (and if the optimize for ad hoc workloads
setting is enabled, it won't suffer from the parameter sniffing issues inherent in the above solution, or plan cache bloat):
DECLARE @sql NVARCHAR(MAX);
SET @sql = N';WITH Ordering AS
(
SELECT ROW_NUMBER() OVER (ORDER BY ' + @SortBy + ' '
+ @SortDirection + ') AS RowNumber,
dbo.x1.*, dbo.x2.*
FROM dbo.x1 INNER JOIN dbo.x2 ON dbo.x1.type = dbo.x2.type
where username = @username
)
SELECT RowNumber, *, DisplayName AS DisplayName
FROM Ordering
Where RowNumber BETWEEN @startRow AND @endRow
ORDER BY RowNumber;';
EXEC sp_executesql @sql,
N'@username NVARCHAR(50), @startRow INT, @endRow INT',
@username, @startRow, @endRow;
Upvotes: 2