我正在尝试编写一个存储过程,该过程返回一个对象列表,其中包含用户选择的排序顺序和排序方向,并作为sql参数传入。
假设我有一个包含以下列的产品表:product_id(int),name(varchar),value(int),created_date(datetime) 和参数@sortDir和@sortOrder
我想做点什么
select *
from Product
if (@sortOrder = 'name' and @sortDir = 'asc')
then order by name asc
if (@sortOrder = 'created_date' and @sortDir = 'asc')
then order by created_date asc
if (@sortOrder = 'name' and @sortDir = 'desc')
then order by name desc
if (@sortOrder = 'created_date' and @sortDir = 'desc')
then order by created_date desc
我尝试使用case语句但由于数据类型不同而遇到了问题。有人有任何建议吗?
答案 0 :(得分:65)
CASE
是一个返回值的表达式。它不适用于流量控制,例如IF
。并且您无法在查询中使用IF
。
不幸的是,CASE
表达式存在一些限制,这使得执行您想要的操作变得很麻烦。例如,CASE
表达式中的所有分支必须返回相同的类型,或者可以隐式转换为相同的类型。我不会尝试使用字符串和日期。您也无法使用CASE
指定排序方向。
SELECT column_list_please
FROM dbo.Product -- dbo prefix please
ORDER BY
CASE WHEN @sortDir = 'asc' AND @sortOrder = 'name' THEN name END,
CASE WHEN @sortDir = 'asc' AND @sortOrder = 'created_date' THEN created_date END,
CASE WHEN @sortDir = 'desc' AND @sortOrder = 'name' THEN name END DESC,
CASE WHEN @sortDir = 'desc' AND @sortOrder = 'created_date' THEN created_date END DESC;
一个可以说更简单的解决方案(特别是如果这变得更复杂)是使用动态SQL。为了阻止SQL注入,您可以测试值:
IF @sortDir NOT IN ('asc', 'desc')
OR @sortOrder NOT IN ('name', 'created_date')
BEGIN
RAISERROR('Invalid params', 11, 1);
RETURN;
END
DECLARE @sql NVARCHAR(MAX) = N'SELECT column_list_please
FROM dbo.Product ORDER BY ' + @sortOrder + ' ' + @sortDir;
EXEC sp_executesql @sql;
动态SQL的另一个好处,尽管所有关于它的恐惧行为:你可以为每个排序变化获得最佳计划,而不是一个单独的计划,它将优化你使用的任何类型变化第一。在我最近进行的性能比较中,它也表现得最好:
答案 1 :(得分:18)
你需要一个案例陈述,虽然我会使用多个案例陈述:
order by (case when @sortOrder = 'name' and @sortDir = 'asc' then name end) asc,
(case when @sortOrder = 'name' and @sortDir = 'desc' then name end) desc,
(case when @sortOrder = 'created_date' and @sortDir = 'asc' then created_date end) asc,
(case when @sortOrder = 'created_date' and @sortDir = 'desc' then created_date end) desc
有四个不同的子句消除了在不同类型之间转换的问题。
答案 2 :(得分:3)
有多种方法可以做到这一点。一种方法是:
SELECT *
FROM
(
SELECT
ROW_NUMBER() OVER ( ORDER BY
CASE WHEN @sortOrder = 'name' and @sortDir = 'asc' then name
END ASC,
CASE WHEN @sortOrder = 'name' and @sortDir = 'desc' THEN name
END DESC,
CASE WHEN i(@sortOrder = 'created_date' and @sortDir = 'asc' THEN created_date
END ASC,
CASE WHEN i(@sortOrder = 'created_date' and @sortDir = 'desc' THEN created_date
END ASC) RowNum
*
)
order by
RowNum
您也可以使用动态SQL执行此操作。
答案 3 :(得分:1)
declare @str varchar(max)
set @str = 'select * from Product order by ' + @sortOrder + ' ' + @sortDir
exec(@str)
答案 4 :(得分:0)
当我尝试做类似的事情时,我偶然发现了这一点,但是我创建的select语句包含了一个相当大且丑陋的字符串连接,这严重限制了我使用动态SQL方法的能力。如果您有简单的搜索案例,这是一个不错的解决方案。
话虽这么说,在SQL Server中执行此操作的另一种方法是使用表变量。这需要更多的开销和复杂性,但同时也为您提供了很大的灵活性。
DECLARE @RawProducts TABLE (
product_id int,
name varchar(50),
value int,
created_date datetime
)
INSERT INTO @RawProducts
SELECT * FROM [Product]
IF @sortOrder = 'name' AND @sortDir = 'asc' BEGIN
SELECT * FROM @RawProducts
ORDER BY [name] ASC
END
IF @sortOrder = 'name' AND @sortDir = 'desc' BEGIN
SELECT * FROM @RawProducts
ORDER BY [name] desc
END
IF @sortOrder = 'created_date' AND @sortDir = 'asc' BEGIN
SELECT * FROM @RawProducts
ORDER BY [created_date] ASC
END
IF @sortOrder = 'created_date' AND @sortDir = 'desc' BEGIN
SELECT * FROM @RawProducts
ORDER BY [created_date] desc
END
该方法的一个缺点是,您需要为要排序的每个案例添加一个case块,但是您必须使用任何不涉及动态SQL的解决方案来做到这一点。
我唯一推荐使用此方法的情况是,如果您的数据集相对较小,并且易于识别搜索案例。