我可以在 JOIN 条件中使用 CASE 语句吗?

Posted

技术标签:

【中文标题】我可以在 JOIN 条件中使用 CASE 语句吗?【英文标题】:Can I use CASE statement in a JOIN condition? 【发布时间】:2012-05-02 15:58:53 【问题描述】:

下图是 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 声明。 谁能帮忙解释一下?


添加错误信息:

消息 102,级别 15,状态 1,第 6 行 '=' 附近的语法不正确。

【问题讨论】:

你用什么软件制作了这个漂亮的 DB 图? @LearnByReading 你有没有发现使用了哪个软件? @User632716 不,很遗憾没有! @User632716 虽然我真的认为是 mysql Workbench。但我从未收到回复 @LearnByReading 我不知道。它由 Microsoft 提供。 【参考方案1】:

根据条件至少有两种加入方式。一个比另一个快:

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)

【讨论】:

【参考方案2】:

是的,你可以。这是一个例子。

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 

【讨论】:

虽然此代码可以解决问题,including an explanation 说明如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请edit您的回答添加解释并说明适用的限制和假设。【参考方案3】:

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

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 

【讨论】:

【参考方案4】:

我拿了你的例子并编辑了它:

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)

【讨论】:

【参考方案5】:

我认为你需要两个案例陈述:

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 语句在 END 处返回单个值 ON 语句比较两个值 您的 CASE 语句正在比较 CASE 语句的内部。我猜如果你把你的 CASE 语句放在你的 SELECT 中,你会得到一个布尔值 '1' 或 '0' 指示 CASE 语句评估为 True 还是 False

【讨论】:

【参考方案6】:

以大金刚为例。

问题是我需要使用已声明的变量。这允许说明您需要比较的左侧和右侧。这是为了支持 s-s-rS 报告,其中必须根据用户的选择链接不同的字段。

初始案例根据选择设置字段选择 然后我可以设置我需要匹配的字段。

可以在右侧添加第二个 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

【讨论】:

【参考方案7】:

这看起来不错

https://bytes.com/topic/sql-server/answers/881862-joining-different-tables-based-condition

FROM YourMainTable
LEFT JOIN AirportCity DepCity ON @TravelType = 'A' and DepFrom =  DepCity.Code
LEFT JOIN AirportCity DepCity ON @TravelType = 'B' and SomeOtherColumn = SomeOtherColumnFromSomeOtherTable

【讨论】:

尝试这个我得到一个错误:相关名称 'xx' 在 FROM 子句中指定了多次。【参考方案8】:

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 谢谢这对我有用......但问题是当我这样做时,条件会失败......请告诉我如何打破它? @SagarTandel - 对不起,我不明白“失败”和“我该如何打破它”。你能澄清你的评论吗? (最近在萨巴岛潜水时浮出水面。可能是高氧。) 它检查所有我不想要的条件。我希望它在符合条件后退出此案。 @SagarTandel - 来自MSDN:“CASE 语句按顺序评估其条件,并在满足条件的第一个条件处停止。”。如果您想要所有与任何明确声明的条件都不匹配的连接行,只需将尾部从 = 1 更改为 = 0,但我认为您不会喜欢结果。 JOIN sys.allocation_units 当 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 你能把它写在你的解决方案上面的实体框架中吗【参考方案9】:

相反,您只需加入两个表,并在您的 SELECT 子句中, 从匹配的那个返回数据:

我建议你通过这个链接Conditional Joins in SQL Server和T-SQL Case Statement in a JOIN ON Clause

例如

    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 

编辑:根据 cmets。

你不能像你一样指定连接条件。检查 上面的查询没有错误。我已经拿出了共同的专栏 并且将根据条件评估右列的值。

【讨论】:

conditional join 是什么意思?每个连接(不包括交叉)都是一个条件。这个案例与其他案例有何不同?您的示例具有带条件的内部连接,并且 OP 查询具有带条件的连接。 @zerkms:我同意,这听起来令人困惑。我相信,在这种情况下,条件连接意味着条件取决于另一个条件的连接。 @Andriy M: 有什么理由想到用OR 来表示琐碎条件的另一个术语? @zerkms:嗯,是的,因为它听起来很混乱。 :) 但我相信你的意思是问是否有任何理由想到另一个术语,在这种情况下我无法确定。如果你问的是我的原因,好吧,我想我实在是太烦了。 :) 加入替代条件怎么样?恐怕我不太擅长造词。但请注意,从概念上讲,这是关于“条件条件”,而不是关于“OR 条件”。使用OR 只是实现它的一种方式。 @Andriy M:好的。但就我个人而言,我仍然看不到用 1 OR 和 2 ANDs 或 1 CASEtrivial 条件命名的理由。这是一个常规查询,与其他查询没有什么不同。【参考方案10】:

试试这个:

...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的代码有什么问题

以上是关于我可以在 JOIN 条件中使用 CASE 语句吗?的主要内容,如果未能解决你的问题,请参考以下文章

SQL里if语句和case语句有啥区别吗?哪个使用更高效?就是查询更优化?

PL/SQL 中 CASE 语句中的堆栈条件

if语句中可以没有case

我可以在case语句中使用列名作为参数吗

在 if 语句中使用 switch case

是否有任何规则可以在条件/案例表达式中使用sql语句子句的哪一部分?