T-SQL条件顺序依据

时间:2013-03-25 18:00:42

标签: sql sql-server tsql

我正在尝试编写一个存储过程,该过程返回一个对象列表,其中包含用户选择的排序顺序和排序方向,并作为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语句但由于数据类型不同而遇到了问题。有人有任何建议吗?

5 个答案:

答案 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的另一个好处,尽管所有关于它的恐惧行为:你可以为每个排序变化获得最佳计划,而不是一个单独的计划,它将优化你使用的任何类型变化第一。在我最近进行的性能比较中,它也表现得最好:

http://sqlperformance.com/conditional-order-by

答案 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的解决方案来做到这一点。

我唯一推荐使用此方法的情况是,如果您的数据集相对较小,并且易于识别搜索案例。