SQL Where子句中的Switch/Case

179

我试图进行搜索,但是无法找到任何有助于我的东西。

我正在尝试在SQL中完成这个操作:

declare @locationType varchar(50);
declare @locationID int;

SELECT column1, column2
FROM viewWhatever
WHERE
CASE @locationType
    WHEN 'location' THEN account_location = @locationID
    WHEN 'area' THEN xxx_location_area = @locationID
    WHEN 'division' THEN xxx_location_division = @locationID

我知道我不应该在每个结尾处加上“= @locationID”,但我无法使语法正确。SQL一直抱怨我的第一个WHEN行上的“=”......

我该怎么做?

14个回答

221
declare @locationType varchar(50);
declare @locationID int;

SELECT column1, column2
FROM viewWhatever
WHERE
@locationID = 
  CASE @locationType
      WHEN 'location' THEN account_location
      WHEN 'area' THEN xxx_location_area 
      WHEN 'division' THEN xxx_location_division 
  END

1
正如TomH在您下面的回复评论中指出的那样,您的SQL语句编写有误。我在SQLServer 2005中测试了我的代码,它可以正常工作。 - Bob Probst
为什么需要 @ 符号?它们有什么作用? - tedi
5
@ 表示 T-SQL 中的变量,如果没有 @,@locationID 将被解释为列名。 - Christian Sloper

82

没有 case 语句...

SELECT column1, column2
FROM viewWhatever
WHERE
    (@locationType = 'location' AND account_location = @locationID)
    OR
    (@locationType = 'area' AND xxx_location_area = @locationID)
    OR
    (@locationType = 'division' AND xxx_location_division = @locationID)

7
由于Case语句在满足条件后会退出,因此这样做将产生略微不同的结果;但OR语法将评估所有可能性。 - tember
1
@tember 即使 SQL 是一种过程化语言,如果第一个 OR 为真,则不会评估表达式的其余部分。由于 SQL 是一种声明性语言,您如何知道 DBM 将评估所有 OR?真诚的问题,我不明白。 - ArturoTena
2
在SQL中,OR语法中的其余表达式确实会被评估。请尝试以下内容(如果要测试它,它不会让我包含@符号-您将不得不更正它): 声明var varchar(5) 设置var ='0' 选择2 / var 其中var <> 0或ISNUMERIC(var)= 1我希望条件退出,因为var等于0,但它继续检查它是否为数字,而它是数字,因此该语句返回错误。 - tember
这个对我很有帮助,因为我针对每种类型都有不同的比较运算符。 - Alex
最好的做法是 `DECLARE @locationType NVARCHAR(50) = 'youchoose'IF @locationType = 'location' BEGIN SELECT column1, column2 FROM viewWhatever WHERE (account_location = @locationID) ENDIF @locationType = 'area' BEGIN SELECT column1, column2 FROM viewWhatever WHERE (xxx_location_area = @locationID) ENDIF @locationType = 'division' BEGIN SELECT column1, column2 FROM viewWhatever WHERE (xxx_location_division = @locationID) END` - Lukek
我所使用的版本将AND与OR交换了。虽然从技术上讲这是可行的,但我发现在SQL 2016实例的许多地方都使用了这种模式,它会强制进行大量的表扫描。优化器对于如何执行空变量持悲观态度,并导致显著的性能问题。因此,我打算用另一种替代方法来取代它们。 - Stradas

52

就是这个了。

SELECT
   column1, 
   column2
FROM
   viewWhatever
WHERE
CASE 
    WHEN @locationType = 'location' AND account_location = @locationID THEN 1
    WHEN @locationType = 'area' AND xxx_location_area = @locationID THEN 1
    WHEN @locationType = 'division' AND xxx_location_division = @locationID THEN 1
    ELSE 0
END = 1

6
我会将其翻译为: 好的,我会写成以下语句: 选择 列1, 列2 从 视图名称 在其中 (@locationType = 'location' AND account_location = @locationID) 或 (@locationType = 'area' AND xxx_location_area = @locationID) 或 (@locationType = 'division' AND xxx_location_division = @locationID) - Jan de Vos
2
这是一个很好的例子,最佳答案的查询执行计划如何呢?(个人偏爱这种方法,代码清晰简洁) - PEO
2
如果你想在不同的where子句中使用不同类型,比如在第一个子句中使用int,在第二个子句中使用nvarchar,那么这真的很棒。但是使用Bob Probst的解决方案并不能实现这一点。谢谢。 - Julian50

7

我认为这表明表结构有缺陷。也许不同的位置类型应该分别放在不同的表中,这样你就可以进行更丰富的查询并避免出现多余的列。

如果无法更改表结构,类似下面的方法可能有效:

SELECT
    *
FROM
    Test
WHERE
    Account_Location = (
        CASE LocationType
          WHEN 'location' THEN @locationID
          ELSE Account_Location
        END
    )
    AND
    Account_Location_Area = (
        CASE LocationType
          WHEN 'area' THEN @locationID
          ELSE Account_Location_Area
        END
    )

等等,我们无法在运行时更改查询的结构,但是我们可以通过使谓词相等来覆盖它。

编辑:上述建议当然要好得多,只需忽略我的建议。


我认为这不是一个有缺陷的表结构。该表被设置成这样,以便它可以自我引用并具有无限数量的父/子关系。相信我,这是有意而为之的。我不认为我想改变我的表结构,只是使用一个开关语句。这并不重要。 - Miles

6
这样做的问题在于,当SQL引擎去评估表达式时,它会检查FROM部分以获取正确的表格,然后检查WHERE部分以提供一些基本条件,因此它无法正确地评估动态条件来检查哪个列。

您可以在谓词中检查WHERE条件时使用WHERE子句,例如:

WHERE account_location = CASE @locationType
                              WHEN 'business' THEN 45
                              WHEN 'area' THEN 52
                         END

在您的情况下,您需要将查询放入存储过程中或创建三个单独的查询。


6

OR 运算符可以在 where 条件中作为 case when 的替代品。

ALTER PROCEDURE [dbo].[RPT_340bClinicDrugInventorySummary]
    -- Add the parameters for the stored procedure here
     @ClinicId BIGINT = 0,
     @selecttype int,
     @selectedValue varchar (50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT
    drugstock_drugname.n_cur_bal,drugname.cdrugname,clinic.cclinicname

FROM drugstock_drugname
INNER JOIN drugname ON drugstock_drugname.drugnameid_FK = drugname.drugnameid_PK
INNER JOIN drugstock_drugndc ON drugname.drugnameid_PK = drugstock_drugndc.drugnameid_FK
INNER JOIN drugndc ON drugstock_drugndc.drugndcid_FK = drugndc.drugid_PK
LEFT JOIN clinic ON drugstock_drugname.clinicid_FK = clinic.clinicid_PK

WHERE   (@ClinicId = 0 AND 1 = 1)
    OR  (@ClinicId != 0 AND drugstock_drugname.clinicid_FK = @ClinicId)

    -- Alternative Case When You can use OR
    AND ((@selecttype = 1 AND 1 = 1)
    OR  (@selecttype = 2 AND drugname.drugnameid_PK = @selectedValue)
    OR  (@selecttype = 3 AND drugndc.drugid_PK = @selectedValue)
    OR  (@selecttype = 4 AND drugname.cdrugclass = 'C2')
    OR  (@selecttype = 5 AND LEFT(drugname.cdrugclass, 1) = 'C'))

ORDER BY clinic.cclinicname, drugname.cdrugname
END

3

请尝试执行此查询。

对上述帖子的回答:
select @msgID, account_id
    from viewMailAccountsHeirachy
    where 
    CASE @smartLocationType
        WHEN 'store' THEN account_location
        WHEN 'area' THEN xxx_location_area 
        WHEN 'division' THEN xxx_location_division 
        WHEN 'company' THEN xxx_location_company 
    END  = @smartLocation

3
给未来读者:这与@bob-prost的被接受答案相同:https://dev59.com/uHVC5IYBdhLWcg3wtzgQ#206500 - ArturoTena

2

试试这个:

WHERE (
    @smartLocationType IS NULL 
    OR account_location = (
         CASE
            WHEN @smartLocationType IS NOT NULL 
                 THEN @smartLocationType
            ELSE account_location 
         END
    )
)

1
因为我不理解这个程序如何在给定的字符串(即“位置”,“区域”,“部门”)之间进行选择,所以我对此进行了负评。 - ArturoTena

0

尝试一下这个查询,它非常简单和有用:已准备好执行!

USE tempdb
GO

IF NOT OBJECT_ID('Tempdb..Contacts') IS NULL
    DROP TABLE Contacts

CREATE TABLE Contacts(ID INT, FirstName VARCHAR(100), LastName VARCHAR(100))
INSERT INTO Contacts (ID, FirstName, LastName)
SELECT 1, 'Omid', 'Karami'
UNION ALL
SELECT 2, 'Alen', 'Fars'
UNION ALL
SELECT 3, 'Sharon', 'b'
UNION ALL
SELECT 4, 'Poja', 'Kar'
UNION ALL
SELECT 5, 'Ryan', 'Lasr'
GO
 
DECLARE @FirstName VARCHAR(100)
SET @FirstName = 'Omid'
 
DECLARE @LastName VARCHAR(100)
SET @LastName = '' 
 
SELECT FirstName, LastName
FROM Contacts
WHERE  
    FirstName = CASE
    WHEN LEN(@FirstName) > 0 THEN  @FirstName 
    ELSE FirstName 
    END
AND
    LastName = CASE
    WHEN LEN(@LastName) > 0 THEN  @LastName 
    ELSE LastName
    END
GO

0
CREATE PROCEDURE [dbo].[Temp_Proc_Select_City]
    @StateId INT
AS  
        BEGIN       
            SELECT * FROM tbl_City 
                WHERE 
                @StateID = CASE WHEN ISNULL(@StateId,0) = 0 THEN 0 ELSE StateId END ORDER BY CityName
        END

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接