【问题标题】:T-SQL Conditional WHERE ClauseT-SQL 条件 WHERE 子句
【发布时间】:2011-05-28 00:33:49
【问题描述】:

在这里找到了几个类似的问题,但不知道如何应用于我的场景。

我的函数有一个名为 @IncludeBelow 的参数。值为 0 或 1 (BIT)。

我有这个问题:

SELECT p.*
FROM Locations l
INNER JOIN Posts p
on l.LocationId = p.LocationId
WHERE l.Condition1 = @Value1
AND   l.SomeOtherCondition = @SomeOtherValue

如果@IncludeBelow 为 0,我需要这样的查询:

SELECT p.*
FROM Locations l
INNER JOIN Posts p
on l.LocationId = p.LocationId
WHERE l.Condition1 = @Value1
AND   l.SomeOtherCondition = @SomeOtherValue
AND   p.LocationType = @LocationType -- additional filter to only include level.

如果@IncludeBelow 为1,则需要排除最后一行。 (即不应用过滤器)。

我猜它应该是 CASE 语句,但无法弄清楚语法。

这是我尝试过的:

SELECT p.*
FROM Locations l
INNER JOIN Posts p
on l.LocationId = p.LocationId
WHERE l.Condition1 = @Value1
AND   l.SomeOtherCondition = @SomeOtherValue
AND (CASE @IncludeBelow WHEN 0 THEN p.LocationTypeId = @LocationType ELSE 1 = 1)

显然这是不正确的。

正确的语法是什么?

【问题讨论】:

    标签: sql sql-server tsql where-clause case-statement


    【解决方案1】:

    我将查询更改为使用 EXISTS,因为如果有多个位置与 POST 相关联,则会有重复的 POST 记录,需要 DISTINCT 或 GROUP BY 子句才能摆脱...

    不可分割的

    这将执行最坏的可能解决方案:

    SELECT p.*
      FROM POSTS p
     WHERE EXISTS(SELECT NULL
                    FROM LOCATIONS l
                   WHERE l.LocationId = p.LocationId
                     AND l.Condition1 = @Value1
                     AND l.SomeOtherCondition = @SomeOtherValue)
       AND (@IncludeBelow = 1 OR p.LocationTypeId = @LocationType)
    

    可搜索的非动态版本

    自我解释....

    BEGIN
      IF @IncludeBelow = 0 THEN
        SELECT p.*
          FROM POSTS p
         WHERE EXISTS(SELECT NULL
                        FROM LOCATIONS l
                       WHERE l.LocationId = p.LocationId
                         AND l.Condition1 = @Value1
                         AND l.SomeOtherCondition = @SomeOtherValue)
           AND p.LocationTypeId = @LocationType
      ELSE
        SELECT p.*
          FROM POSTS p
         WHERE EXISTS(SELECT NULL
                        FROM LOCATIONS l
                       WHERE l.LocationId = p.LocationId
                         AND l.Condition1 = @Value1
                         AND l.SomeOtherCondition = @SomeOtherValue) 
    END
    

    可搜索的动态版本(SQL Server 2005+):

    喜欢或讨厌它,动态 SQL 让您只需编写一次查询。请注意 sp_executesql 缓存查询计划,这与 SQL Server 中的 EXEC 不同。强烈建议在考虑 SQL Server 上的动态 SQL 之前阅读 The Curse and Blessings of Dynamic SQL...

    DECLARE @SQL VARCHAR(MAX)
        SET @SQL = 'SELECT p.*
                      FROM POSTS p
                     WHERE EXISTS(SELECT NULL
                                    FROM LOCATIONS l
                                   WHERE l.LocationId = p.LocationId
                                     AND l.Condition1 = @Value1
                                     AND l.SomeOtherCondition = @SomeOtherValue)'
    
        SET @SQL = @SQL + CASE 
                            WHEN @IncludeBelow = 0 THEN
                             ' AND p.LocationTypeId = @LocationType '
                            ELSE ''
                          END   
    
    BEGIN 
    
      EXEC sp_executesql @SQL, 
                         N'@Value1 INT, @SomeOtherValue VARCHAR(40), @LocationType INT',
                         @Value1, @SomeOtherValue, @LocationType
    
    END
    

    【讨论】:

    • 非常好。我不是 DSQL 的粉丝(可以避免),并且“sargable”解决方案会起作用,但请记住,我提供的代码已针对问题进行了简化。我有大约 6 个连接 - (发布位置不是直接关系 - 有一个连接表 - 多对多)。所以我必须在EXISTS 子句中重复所有这些。因此,重复不是问题(不需要分组)。我不想展示我的整个代码,因为它会让每个人都感到困惑。感谢您的回答 - 非常有见地和完整(一如既往)。 +1 并被接受。
    • Lolz,如果您看过原始代码,您可能会惊掉下巴。我有临时表、UDT、三个 LOJ 到 TVF、CASE、MAX、作品。她不漂亮。 :)
    • 动态sql部分的case部分是否需要读取SET @SQL = @SQL + CASE...。它需要与原始连接,对吗?
    • 在 non-sargeable 版本中,您假设我读过的布尔短路工作并不能保证。您是否 100% 确定代码将始终按预期执行?
    【解决方案2】:

    你可以写成

    SELECT  p.*
      FROM  Locations l
    INNER JOIN Posts p
        ON  l.LocationId = p.LocationId
      WHERE l.Condition1 = @Value1
        AND l.SomeOtherCondition = @SomeOtherValue
        AND ((@IncludeBelow = 1) OR (p.LocationTypeId = @LocationType))
    

    这是您经常看到的模式,例如对于可选的搜索参数。但是 IIRC 可能会弄乱查询执行计划,因此可能有更好的方法来做到这一点。

    由于它只是一点点,几乎可能值得在两个 SQL 块之间决定是否有检查,例如在存储过程中使用 IF 或在调用代码中使用不同的命令字符串,基于位?

    【讨论】:

    • 是的,我考虑过 IF,但实际上我的查询要大得多。当所有不同的只是 1 行时,我不想维护两个巨大的 T-SQL 块。您上面的代码工作并且执行得相当好。将在 7 分钟内接受这个答案(除非有人想出另一个答案来解释为什么他们的答案比这个更好)。谢谢。
    • 这不是 sargable - 它不会表现良好。
    • @OMG Ponies 嘿,我正要链接到你的这个旧答案stackoverflow.com/questions/3649036/…
    • 我在这里有点搞砸了:stackoverflow.com/questions/4408551/… 我听说它可以使查询成为非 SARGable,但这个例子是我见过的少数几个之一有显着差异。
    • @RPM1984 谢谢!不,完全同意 - 并且很高兴自己学习其他方法。
    【解决方案3】:

    您可以将您的CASE 声明更改为此。查询规划器对此有不同的看法,但它可能并不比使用 OR 更有效:

    (p.LocationTypeId = CASE @IncludeBelow WHEN 0 THEN p.LocationTypeId ELSE @LocationType END)
    

    【讨论】:

      【解决方案4】:

      编辑sql语句如下:

      SELECT p.*
      FROM Locations l
          INNER JOIN Posts p
          on l.LocationId = p.LocationId
      WHERE l.Condition1 = @Value1
          AND l.SomeOtherCondition = @SomeOtherValue
          AND l.LocationType like @LocationType
      

      不需要@IncludeBelow 变量

      包含所有位置类型设置@LocationType = '%'

      限制查询返回的位置类型 设置@LocationType = '[特定位置类型]'

      上面的 Set 语句假设 @LocationType 变量是字符数据类型

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多