我能在JOIN语句中使用CASE语句吗?

195

以下图片是 Microsoft SQL Server 2008 R2 系统视图的一部分。从图片中我们可以看到 sys.partitionssys.allocation_units 之间的关系取决于 sys.allocation_units.type 的值。因此,为了将它们连接起来,我会写出类似于这样的代码:

SELECT  *
FROM    sys.indexes i
        JOIN sys.partitions p
            ON i.index_id = p.index_id 
        JOIN sys.allocation_units a
            ON CASE
               WHEN a.type IN (1, 3)
                   THEN a.container_id = p.hobt_id 
               WHEN a.type IN (2)
                   THEN a.container_id = p.partition_id
               END 

但是上面的代码会出现语法错误。我猜这是由于CASE语句引起的。有人可以帮忙解释一下吗?


添加错误信息:

Msg 102,Level 15,State 1,Line 6 Incorrect syntax near '='.

这是图片


50
你用什么软件制作了这个漂亮的数据库图表? - LearnByReading
3
@LearnByReading,你最终找出使用的是哪个软件了吗? - Preston
1
@User632716 不好意思,不是的! - LearnByReading
3
虽然我确实认为那是MySQL Workbench,但我从未收到回复。 - LearnByReading
3
看起来很像Visual Paradigm - Harry Jones
显示剩余2条评论
11个回答

316

CASE 表达式从 THEN 部分返回一个值。您可以像这样使用它:

SELECT  * 
FROM    sys.indexes i 
    JOIN sys.partitions p 
        ON i.index_id = p.index_id  
    JOIN sys.allocation_units a 
        ON CASE 
           WHEN a.type IN (1, 3) AND a.container_id = p.hobt_id THEN 1
           WHEN a.type IN (2) AND a.container_id = p.partition_id THEN 1
           ELSE 0
           END = 1

请注意,您需要对返回的值进行某些操作,例如将其与1进行比较。您的语句试图返回赋值或相等性测试的值,在CASE/THEN子句的上下文中都没有意义。(如果BOOLEAN是一种数据类型,则相等性测试将有意义。)


@HABO 谢谢,这对我有用...但问题是当我这样做时,条件会导致一个 "fall through" 的情况,请告诉我如何打破它? - Sagar Tandel
1
@SagarTandel - 不好意思,我不理解"make a fall through"和"how do I break it"。你能否澄清一下你的评论?(最近从萨巴岛潜水冒出来,可能是因为混合氧气的关系。) - HABO
1
它检查了我不想要的所有条件。我希望它在匹配一个条件后退出该情况。 - Sagar Tandel
4
根据MSDN上的描述,CASE语句会按顺序逐个评估其条件,并在满足条件的第一个条件停止。如果想要获取所有未匹配任何明确说明条件的连接行,则只需将尾部的 "= 1" 改为 "= 0",但我认为您可能不会喜欢结果。 - HABO
在你的解决方案中,能否使用Entity Framework编写以下内容:JOIN sys.allocation_units a ON CASE WHEN a.type IN (1, 3) AND a.container_id = p.hobt_id THEN 1 WHEN a.type IN (2) AND a.container_id = p.partition_id THEN 1 ELSE 0 END = 1 - r.hamd
@r.hamd 抱歉,但我一直避免使用EF。 - HABO

60
相反,您只需加入两个表格,在SELECT子句中返回匹配的数据:
我建议您阅读以下链接:SQL Server中的条件连接JOIN ON子句中的T-SQL Case语句 例如:
    SELECT  *
FROM    sys.indexes i
        JOIN sys.partitions p
            ON i.index_id = p.index_id 
        JOIN sys.allocation_units a
            ON a.container_id =
            CASE
               WHEN a.type IN (1, 3)
                   THEN  p.hobt_id 
               WHEN a.type IN (2)
                   THEN p.partition_id
               END 

编辑:根据评论。

你不能像你现在这样指定连接条件。检查上面的查询,没有错误。我已经去掉了公共列,右侧列的值将根据条件进行评估。


1
什么是“条件联接”?每个联接(除了交叉联接)都是有条件的。这种情况与其他任何情况有何不同?您的示例具有带条件的内部联接,以及OP的查询具有带条件的联接。 - zerkms
@zerkms:我同意,这听起来很混乱。我认为,在这种情况下,“条件连接”意味着连接的条件取决于另一个条件。 - Andriy M
@Andriy M:有没有考虑用另一个术语来表示带有“OR”的平凡条件的原因? - zerkms
@Andriy M:好的。但就我个人而言,我仍然不明白为什么要给一个只有1个“OR”和2个“AND”的平凡条件或者一个“CASE”命名。这是一个例行查询,与任何其他查询没有区别。 - zerkms
这两者的区别在于OPs只有一个参数,SQL实现中则是有一个名为"something"的参数并赋值为(case whatever)。 Join on (case whatever) - 这是OPs中的语句。 Join on something = (case whatever) - 这是SQL实现中的语句。 - Anon343224user
显示剩余2条评论

19

试一下这个:

...JOIN sys.allocation_units a ON 
  (a.type=2 AND a.container_id = p.partition_id)
  OR (a.type IN (1, 3) AND a.container_id = p.hobt_id)

尽管它可以工作-问题中的查询看起来完全有效。所以仍然无法解释OP代码的问题在哪里。 - zerkms

12

我认为您需要两个case语句:

SELECT  *
FROM    sys.indexes i
    JOIN sys.partitions p
        ON i.index_id = p.index_id 
    JOIN sys.allocation_units a
        ON 
        -- left side of join on statement
            CASE
               WHEN a.type IN (1, 3)
                   THEN a.container_id
               WHEN a.type IN (2)
                   THEN a.container_id
            END 
        = 
        -- right side of join on statement
            CASE
               WHEN a.type IN (1, 3)
                   THEN p.hobt_id
               WHEN a.type IN (2)
                   THEN p.partition_id
            END             
这是因为:
  • CASE语句在结尾处返回单个值
  • ON语句比较两个值
  • 你的CASE语句在CASE语句内部进行比较。我猜如果你将CASE语句放在SELECT中,你会得到一个布尔值'1'或'0',表示CASE语句是否为真或假

6
我采用了你的示例并进行了编辑:
SELECT  *
FROM    sys.indexes i
    JOIN sys.partitions p
        ON i.index_id = p.index_id 
    JOIN sys.allocation_units a
        ON a.container_id = (CASE
           WHEN a.type IN (1, 3)
               THEN p.hobt_id 
           WHEN a.type IN (2)
               THEN p.partition_id
           ELSE NULL
           END)

6

可以的,这里有一个例子。

SELECT a.*
FROM TableA a
LEFT OUTER JOIN TableB j1 ON  (CASE WHEN LEN(COALESCE(a.NoBatiment, '')) = 3 
                                THEN RTRIM(a.NoBatiment) + '0' 
                                ELSE a.NoBatiment END ) = j1.ColumnName 

1
虽然这段代码可能解决了问题,但是包括解释它如何以及为什么解决了问题将有助于提高您的帖子质量,并可能导致更多的赞。请记住,您正在回答未来读者的问题,而不仅仅是现在提问的人。请[编辑]您的答案以添加解释并指出适用的限制和假设。 - double-beep

2

尝试这个时,我遇到了一个错误:在FROM子句中指定了多个相同的关联名称“xx”。 - Etienne

2

至少有两种基于条件连接的方法,其中一种比另一种更快:

declare @loopZaKosovnice int = 1
select * 
from tHE_MoveItem mi 
left join tHE_SetProdSt st on st.acIdent = mi.acIdent

-- slow
--join the_setitem si on si.acident = case when @loopZaKosovnice = 0 then mi.acident else st.acIdentChild end 

-- two times as fast
left join the_setitem si1 on @loopZaKosovnice = 0 and si1.acident = mi.acident
left join the_setitem si2 on @loopZaKosovnice = 1 and si2.acident = st.acIdentChild
join the_setitem si on si.acident = isnull (si1.acident, si2.acIdent)

1

在这里,我比较了两个不同结果集之间的差异:

SELECT main.ColumnName, compare.Value PreviousValue,  main.Value CurrentValue
FROM 
(
    SELECT 'Name' AS ColumnName, 'John' as Value UNION ALL
    SELECT 'UserName' AS ColumnName, 'jh001' as Value UNION ALL
    SELECT 'Department' AS ColumnName, 'HR' as Value UNION ALL
    SELECT 'Phone' AS ColumnName, NULL as Value UNION ALL
    SELECT 'DOB' AS ColumnName, '1993-01-01' as Value UNION ALL
    SELECT 'CreateDate' AS ColumnName, '2017-01-01' as Value UNION ALL
    SELECT 'IsActive' AS ColumnName, '1' as Value
) main
INNER JOIN
(
    SELECT 'Name' AS ColumnName, 'Rahul' as Value UNION ALL
    SELECT 'UserName' AS ColumnName, 'rh001' as Value UNION ALL
    SELECT 'Department' AS ColumnName, 'HR' as Value UNION ALL
    SELECT 'Phone' AS ColumnName, '01722112233' as Value UNION ALL
    SELECT 'DOB' AS ColumnName, '1993-01-01' as Value UNION ALL
    SELECT 'CreateDate' AS ColumnName, '2017-01-01' as Value UNION ALL
    SELECT 'IsActive' AS ColumnName, '1' as Value
) compare
ON main.ColumnName = compare.ColumnName AND
CASE 
    WHEN main.Value IS NULL AND compare.Value IS NULL THEN 0
    WHEN main.Value IS NULL AND compare.Value IS NOT NULL THEN 1
    WHEN main.Value IS NOT NULL AND compare.Value IS NULL THEN 1
    WHEN main.Value <> compare.Value THEN 1
END = 1 

0

参考DonkeyKong的例子。

问题在于我需要使用一个已声明的变量。这允许声明你需要比较的左右两侧。这是为了支持一个SSRS报告,其中不同的字段必须基于用户的选择进行链接。

最初的情况根据选择设置字段选择,然后我可以设置我需要匹配的连接字段。

如果需要从不同的字段中选择,则可以添加第二个case语句作为右侧。

LEFT OUTER JOIN Dashboard_Group_Level_Matching ON
       case
         when @Level  = 'lvl1' then  cw.Lvl1
         when @Level  = 'lvl2' then  cw.Lvl2
         when @Level  = 'lvl3' then  cw.Lvl3
       end
    = Dashboard_Group_Level_Matching.Dashboard_Level_Name

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