【问题标题】:Using CASE in Order By Clause按子句顺序使用 CASE
【发布时间】:2010-07-25 18:55:22
【问题描述】:

我有以下 SQL:

ALTER PROCEDURE [dbo].[SP_Products_GetList]


@CatID int,
@CatName int,
@SortBy varchar(50),
@SortType varchar(50)

AS


SELECT Products.ProductID, ProductName, MAX(Price) Price FROM Products 
    INNER JOIN  ProductCategory
        on Products.ProductID = ProductCategory.ProductID 
    INNER JOIN  (
                    SELECT * FROM Categories 
                        WHERE 
                            ( @CatID is null or @CatID = CatID ) and
                            ( @CatName is null or @CatName = CatName )
                ) Categories 
        on ProductCategory.CatID = Categories.CatID 
    INNER JOIN ( 
                    SELECT ProductID, max(Price) Price  from Prices WHERE PriceID IN 
                            ( SELECT MAX(PriceID) FROM Prices 
                            GROUP BY ProductID , SizeID)
                    GROUP BY ProductID
                ) as Prices 
        on Prices.ProductID = Products.ProductID 
GROUP BY ProductName, CatName, Products.ProductID, Price
ORDER BY 
CASE @SortType 
    WHEN 'desc' THEN  
    CASE @SortBy 
        WHEN 'ProductID' THEN Products.ProductID 
        WHEN 'ProductName' THEN ProductName
        WHEN 'Price' THEN Price  
        END 
    END 

执行通过

EXEC    [dbo].[SP_Products_GetList]
        @CatID = 1,
        @CatName = NULL,
        @SortType = 'DESC',
        @Sortby = 'ProductID'

执行失败

EXEC    [dbo].[SP_Products_GetList]
        @CatID = 1,
        @CatName = NULL,
        @SortType = 'DESC',
        @Sortby = 'ProductName'

消息 245,级别 16,状态 1,过程 SP_Products_GetList,第 13 行 将 varchar 值“01-My First Tools Diaper Cake”转换为数据类型 int 时转换失败。

当我不区分大小写更改查询并编写简单时:

.....
ORDER BY ProductName

效果很好

为什么要尝试将varchar 转换为int,如错误消息中所示?

【问题讨论】:

  • @Mark:不要问你不想知道答案的问题。

标签: sql-server tsql


【解决方案1】:

case 语句的输出类型是int,因为这是表达式的第一个元素的类型 (Products.ProductID)。为了使其正常工作,您必须将每个值显式转换为 varchar(这意味着您还必须在值前面加上零,以便它们正确排序)。

你最好这样做:

ORDER BY 
    CASE WHEN @SortBy = 'ProductID' THEN Products.ProductID ELSE NULL END,
    CASE WHEN @SortBy = 'ProductName' THEN ProductName ELSE NULL END,
    CASE WHEN @SortBy = 'Price' THEN Price ELSE NULL END

显然,这没有考虑方向(ASCDESC),但这应该很容易添加。

【讨论】:

    【解决方案2】:

    您需要按类型分隔 case 语句。例如,为您的 int 列设置一个 case 语句。为您的 varchar 类型提供第二个 case 语句。您在 case 语句中首先列出了 ProductID,它是一个 int,因此它将使用它作为 case 语句的数据类型。

    【讨论】:

      【解决方案3】:

      当您使用 CASE 语句时,输出必须是特定类型的值/列。在 Price 的情况下,这很可能不是 VARCHAR 数据类型。

      有几种方法可以有效地做到这一点。

      我个人最喜欢的是将查询的可重用部分放在 order by 上方,并将其放入内联表值函数中。

      然后在你的 SP 中:

      IF (@OrderBy = 'COL1')
          SELECT *
          FROM UDF(params)
          ORDER BY COL1
      ELSE IF (@OrderBy = 'COL2')
          SELECT *
          FROM UDF(params)
          ORDER BY COL2
      

      这将有最有效的执行计划(实际上它将有一个非常有效的条件计划),因为它不会尝试转换每一列或做一些花哨的变通方法,例如在某些情况下对没有数据的列进行排序。

      就您不想重复的复杂代码而言,它是相对模块化的。

      【讨论】:

      • @OMG:当然不是;最好的方法是为每个排序子句编写单独的查询。我不知道我是否建议添加仅与一个查询/存储过程相关的其他架构对象。
      • @Adam Robinson:你显然不知道动态 SQL 是什么——我建议你阅读The curse and blessing of dynamic SQL (SQL Server specific)。没有“其他模式对象”,在此示例中,还有其他可选参数由于 OR 和缺乏可搜索性而表现不佳。
      • @OMG:我很好奇为什么我“显然不知道什么是动态 SQL”;我当然愿意。尽管如此,拥有三个不同的预编译存储过程,它们的区别仅在于 sort 子句被一个检查 sort 子句变量的过程动态调用,事实上,它的性能优于动态 SQL。
      • @Adam Robinson:你让自己感到尴尬 - 查询不是“预编译”的,它们的执行路径会在有限的时间内存储。谷歌“硬/软解析”。
      • @OMG:我不确定你的尖酸刻薄是从哪里来的,但无论如何,是的,一个查询 plan (不是路径,如果我们要挑剔彼此无关的词汇错误)是编译和缓存的内容(尽管您可以控制编译和缓存过程计划的时间和时间)。无论如何,如果你不能保持尊重的态度,我不会进一步参与谈话。
      【解决方案4】:

      此时 case 语句解析为 INT,因为 ProductId 返回一个 INT。您可以重写它以将其转换为 VARCHAR 以避免该问题。

      ORDER BY  
      CASE @SortType  
          WHEN 'desc' THEN   
          CASE @SortBy  
              WHEN 'ProductID' THEN CONVERT(VARCHAR(MAX), Products.ProductID)
              WHEN 'ProductName' THEN CONVERT(VARCHAR(MAX), ProductName)
              WHEN 'Price' THEN CONVERT(VARCHAR(MAX), Price)
              END  
          END  
      

      我使用 VARCHAR(MAX) 只是因为我不知道 ProductName 字段的长度。您可以将其更改为 ProductName 字段的长度。

      您似乎正在尝试动态生成 SQL。 T-SQL 在这方面不是很好。从性能角度来看,最有效的方法是构建查询字符串,然后执行它。在某些情况下,这是一种很好的技术,但您的代码就足够了。如果它是动态生成的 SQL,这是您的代码示例:

      DECLARE @SqlString NVARCHAR(MAX) = '';
      SET @SqlString = N'SELECT Products.ProductID, ProductName, MAX(Price) Price FROM Products  
          INNER JOIN  ProductCategory 
              on Products.ProductID = ProductCategory.ProductID  
          INNER JOIN  ( 
                          SELECT * FROM Categories  
                              WHERE  
                                  ( @CatID is null or @CatID = CatID ) and 
                                  ( @CatName is null or @CatName = CatName ) 
                      ) Categories  
              on ProductCategory.CatID = Categories.CatID  
          INNER JOIN (  
                          SELECT ProductID, max(Price) Price  from Prices WHERE PriceID IN  
                                  ( SELECT MAX(PriceID) FROM Prices  
                                  GROUP BY ProductID , SizeID) 
                          GROUP BY ProductID 
                      ) as Prices  
              on Prices.ProductID = Products.ProductID  
      GROUP BY ProductName, CatName, Products.ProductID, Price 
      ORDER BY  ' + @SortBy + ' ' + @SortType;
      
      EXEC sp_ExecuteSQL @SqlString;
      

      上面的代码并不完全正确。中间的参数化部分可能应该重新设计,否则您需要将值作为参数传递给 sp_executesql 命令。使用动态生成的 SQL 有两个缺点。 1、代码可能受到SQL注入攻击。如果这是一份内部报告,那么这似乎不太可能成为问题。第二个缺点是更难理解。看看代码就明白我的意思了!

      【讨论】:

      • -1 不要这样做;将数值转换为字符串将按字典顺序对它们进行排序,不是按数字排序(意味着10 将被视为比2“小”)。为了使其正常工作,您必须在左侧用零填充值,零的数量由您期望在小数点左侧看到的最大位数确定。
      • 此问题的问题是alpha排序顺序与数字排序顺序选择价格时,您将获得1,11,12,2,21,22,3等。 span>
      猜你喜欢
      • 2019-12-16
      • 1970-01-01
      • 1970-01-01
      • 2014-08-23
      • 2012-05-25
      • 1970-01-01
      • 1970-01-01
      • 2014-09-10
      • 1970-01-01
      相关资源
      最近更新 更多