191

The following image is a part of Microsoft SQL Server 2008 R2 System Views. From the image we can see that the relationship between sys.partitions and sys.allocation_units depends on the value of sys.allocation_units.type. So to join them together I would write something similar to this:

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 

But the upper code gives a syntax error. I guess that's because of the CASE statement. Can anyone help to explain a little?


Add error message:

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

this is the image

thepurpleowl
  • 147
  • 4
  • 15
Just a learner
  • 26,690
  • 50
  • 155
  • 234

11 Answers11

312

A CASE expression returns a value from the THEN portion of the clause. You could use it thusly:

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

Note that you need to do something with the returned value, e.g. compare it to 1. Your statement attempted to return the value of an assignment or test for equality, neither of which make sense in the context of a CASE/THEN clause. (If BOOLEAN was a datatype then the test for equality would make sense.)

HABO
  • 15,314
  • 5
  • 39
  • 57
  • @HABO thanks that worked for me...but the problem is when i do this the conditions make a fall through...please tell me how do i break it? – Sagar Tandel Oct 17 '13 at 18:59
  • 1
    @SagarTandel - Sorry, I don't understand "make a fall through" and "how do I break it". Could you clarify your comment? (Recently surfaced from a dive off Saba. Might be the Nitrox.) – HABO Oct 17 '13 at 21:39
  • 1
    It checks for all of the conditions which i don't want. I want it to quit the case once it matches a condition. – Sagar Tandel Oct 18 '13 at 06:11
  • 4
    @SagarTandel - From [MSDN](http://technet.microsoft.com/en-us/library/ms181765.aspx?ppud=4): "The CASE statement evaluates its conditions sequentially and stops with the first condition whose condition is satisfied.". If you want all of the joined rows that don't match any of the explicitly stated conditions, just change the tail end from `= 1` to `= 0`, but I don't think you'll like the result. – HABO Oct 18 '13 at 19:13
  • 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 could you write it in Entity framework the above of your solution – r.hamd Sep 23 '15 at 13:01
  • @r.hamd Sorry, but I have studiously avoided EF. – HABO Sep 23 '15 at 13:44
59

Instead, you simply JOIN to both tables, and in your SELECT clause, return data from the one that matches:

I suggest you to go through this link Conditional Joins in SQL Server and T-SQL Case Statement in a JOIN ON Clause

e.g.

    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 

Edit: As per comments.

You can not specify the join condition as you are doing.. Check the query above that have no error. I have take out the common column up and the right column value will be evaluated on condition.

Community
  • 1
  • 1
Niranjan Singh
  • 18,017
  • 2
  • 42
  • 75
  • 1
    what `conditional join` means? Each join (excluding cross) is a conditional. How this case is different from any other? Your sample has inner join with condition, as well as OPs query has join with condition. – zerkms Apr 21 '12 at 06:48
  • @zerkms: I agree, it sounds confusing. I believe, a *conditional join* in this context means a join whose condition depends on another condition. – Andriy M Apr 21 '12 at 18:44
  • @Andriy M: any reason to think of another term for trivial condition with `OR`? – zerkms Apr 21 '12 at 21:25
  • @zerkms: Er, yes, it's because it sounds confusing. :) But I believe you meant to ask if there was any reason **not** think of another term, in which case I cannot be sure. If you are asking for *my* reasons, well, I think I just couldn't be bothered enough. :) How about a *join with alternate conditions*? I'm afraid I'm not very good at coining terms. Note, though, that conceptually this is about "conditional condition", not about "condition with `OR`". Using `OR` is merely one way of implementing it. – Andriy M Apr 21 '12 at 21:52
  • @Andriy M: ok. But personally I still don't see the reason to give a name for the **trivial** condition with 1 `OR` and 2 `ANDs` or with 1 `CASE`. It is a routine query that has nothing that differs it from any other. – zerkms Apr 21 '12 at 22:16
  • The difference between this and OPs this: OP: Join on (case whatever) This: Join on something = (case whatever) To the SQL implementation OPs has one argument – Anon343224user Dec 04 '13 at 12:50
  • What would happen if `container_id` wasn't 1, 2, or 3? Would the condition become `container_id = NULL`? – Stevoisiak Jul 23 '20 at 20:57
18

Try this:

...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)
richardtallent
  • 34,724
  • 14
  • 83
  • 123
  • Even though it would work - the query in the question looks perfectly valid. So still doesn't explain what's wrong with OP's code – zerkms Apr 21 '12 at 06:42
12

I think you need two case statements:

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             

This is because:

  • the CASE statement returns a single value at the END
  • the ON statement compares two values
  • your CASE statement was doing the comparison inside of the CASE statement. I would guess that if you put your CASE statement in your SELECT you would get a boolean '1' or '0' indicating whether the CASE statement evaluated to True or False
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
DonkeyKong
  • 1,005
  • 14
  • 18
6

I took your example and edited it:

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)
Rutek
  • 3
  • 3
Gont
  • 61
  • 1
  • 1
6

Yes, you can. Here is an example.

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 
Stefan Gabor
  • 340
  • 3
  • 7
  • 1
    While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. – double-beep May 26 '20 at 17:27
2

This seems nice

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
  • Trying this I'm getting an error: The correlation name 'xx' is specified multiple times in a FROM clause. – Etienne Apr 27 '17 at 07:19
2

There are at least 2 ways to join based on condition. One is faster than the other:

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)
Tone Škoda
  • 1,463
  • 16
  • 20
1

Here I have compared the difference in two different result sets:

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 
double-beep
  • 5,031
  • 17
  • 33
  • 41
Sheikh Kawser
  • 136
  • 2
  • 6
0

Took DonkeyKong's example.

The issue is I needed to use a declared variable. This allows for stating your left and right-hand side of what you need to compare. This is for supporting an SSRS report where different fields must be linked based on the selection by the user.

The initial case sets the field choice based on the selection and then I can set the field I need to match on for the join.

A second case statement could be added for the right-hand side if the variable is needed to choose from different fields

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
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
0

While it is possible as others have shown to make use of case expressions in join conditions -- that would wreak havoc on any use of indexes etc., depending.

I'd suggest a better way of approaching is to simply break out the conditions and union-all them together:

SELECT
    *
FROM sys.indexes                    AS i
    INNER JOIN sys.partitions       AS p
        ON i.index_id = p.index_id

    INNER JOIN sys.allocation_units AS a
        ON p.hobt_id  = a.container_id
WHERE a.type IN ( 1, 3 )

UNION ALL

SELECT
    *
FROM sys.indexes                    AS i
    INNER JOIN sys.partitions       AS p
        ON i.index_id     = p.index_id

    INNER JOIN sys.allocation_units AS a
        ON p.partition_id = a.container_id
WHERE a.type = 2