Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQL Where Clause - executes ONLY WHEN certain @Parameters are equal to specific values

Tags:

sql

tsql

DECLARE @T TABLE  
(  
  ID BIGINT IDENTITY PRIMARY KEY,   
  FaceBookID BIGINT NULL,   
  TwitterID BIGINT NULL,  
  LinkedInID VARCHAR(50) NULL  
);  


INSERT INTO @T (FaceBookID, TwitterID, LinkedInID)  
VALUES (11111111, NULL, NULL)  

INSERT INTO @T (FaceBookID, TwitterID, LinkedInID)   
VALUES (NULL, 22222222, NULL)  

INSERT INTO @T (FaceBookID, TwitterID, LinkedInID)  
VALUES (NULL, NULL, '3333333')  


DECLARE @UserType VARCHAR(10)  
SET @UserType = 'LinkedIn'  

DECLARE @oAuthID VARCHAR(50)  
SET @oAuthID = 'aaaaaaa'  


DECLARE @UserID BIGINT  

SELECT @UserID = (  
  SELECT ID FROM @T  
    WHERE (@UserType = 'FaceBook' AND [FaceBookID] = CAST(@oAuthID AS BIGINT))  
       OR (@UserType = 'Twitter' AND [TwitterID] = CAST(@oAuthID AS BIGINT))   
       OR (@UserType = 'LinkedIn' AND [LinkedInID] = @oAuthID)  
)           

SELECT @UserID  

PROBLEM:
Even when the UserType is 'LinkedIn' the first WHERE clause gets executed completely and the SQL tries to CAST the value of @oAuthID, which in this case is 'aaaaaaa', into a BIGINT.

QUESTION:
How can I write a WHERE CLAUSE where IF the first part is NOT TRUE, the second one is NOT executed?
Ideally, because @UserType is NOT 'FaceBook', we should NOT try to calculate the second part of that line (the CAST).

like image 227
SF Developer Avatar asked Dec 12 '22 08:12

SF Developer


2 Answers

While sql does do short circuiting (the feature that allows a language to only evaluate one part of a condition), it does not do it in the same way as other languages.

Sql Server uses an optimizer to build an execution plan for a query. The optimizer looks at the query, and tries to build it in the most efficient way possible. Most of the time it optimizes for the estimated number of results or best index use: it wants to keep fewer records locked at a time and fewer records in memory at a time. But when sets from two conditions are about the same size or match similar indexes, it can also mean things like checking indexed columns in the where clause before non-indexed columns, or making small (and therefore fast) comparisons like bits or integers before longer (and therefore slower) comparisons (like string matches).

In this case, your best chance is to treat both sides as a text type (varchar, nchar, etc) and use that for your match. It will be slower, but you must always code for correctness first, and performance second.

This should work:

WHERE @oAuthID = CASE WHEN @UserType = 'Facebook' THEN Cast(FaceBookID as varchar(50))
                           WHEN @UserType = 'Twitter' THEN Cast(TwitterID as varchar(50))
                           WHEN @UserType = 'LinkedIn' THEN Cast(LinkedInID as varchar(50))
                           ELSE 'invalid user type' /* could use NULL here - as long as you'll never actually see a query with this value */
                           END
like image 52
Joel Coehoorn Avatar answered Feb 08 '23 23:02

Joel Coehoorn


Change your query to this:

SELECT @UserID = (  
  SELECT ID FROM @T  
    WHERE  (@UserType = 'FaceBook' AND [FaceBookID] = CASE WHEN ISNUMERIC(@oAuthID) = 1 THEN CAST(@oAuthID AS BIGINT) ELSE NULL END)  
       OR  (@UserType = 'Twitter' AND [TwitterID] = CASE WHEN ISNUMERIC(@oAuthID) = 1 THEN CAST(@oAuthID AS BIGINT) ELSE NULL END)   
       OR (@UserType = 'LinkedIn' AND [LinkedInID] = @oAuthID)  
)           

Notice, where I put null on the else leg, assign the default of your choice.

like image 34
Chris Townsend Avatar answered Feb 09 '23 00:02

Chris Townsend