SQL Server Conditional CASE JOIN query - sql-server
Can someone help me out on Conditional join?
I want to join on ID and [Name] from both tables and next condition is like
CASE WHEN a.Count = b.Count THEN a.Rn1 = b.Rn1 WHEN a.Count <>
b.Count THEN a.Rn2 = b.Rn2 and a.Rn3 = b.Rn3 END
DROP TABLE IF EXISTS #Test1
DROP TABLE IF EXISTS #Test2
CREATE TABLE #Test1 (ID int, [name] varchar(50),[Count] int, Rn1 int, Rn2 int, Rn3 int)
CREATE TABLE #Test2 (ID int, [name] varchar(50),[Count] int, Rn1 int, Rn2 int, Rn3 int)
Insert Into #Test1
Values
(123123,'Hours',6,1,1,1)
,(123123,'Hours',6,2,1,2)
,(123123,'Hours',6,3,2,1)
,(123123,'Hours',6,4,3,1)
,(123123,'Hours',6,5,3,2)
,(123123,'Hours',6,6,4,1)
,(123123,'NI1',1,1,1,1)
,(123123,'NI2',1,1,1,1)
,(123123,'PAY',1,1,1,1)
,(123123,'Teachers1',1,1,1,1)
,(123123,'Teachers2',1,1,1,1)
,(123124,N'Hours',5,1,1,1)
,(123124,N'Hours',5,2,2,1)
,(123124,N'Hours',5,3,3,1)
,(123124,N'Hours',5,4,3,2)
,(123124,N'Hours',5,5,4,1)
,(123124,N'NI1',1,1,1,1)
,(123124,N'NI2',1,1,1,1)
,(123124,N'PAY',1,1,1,1)
--SELECT * FROM #Test1
Insert Into #Test2
Values (123123,N'Hours',6,1,1,1)
,(123123,N'Hours',6,2,1,2)
,(123123,N'Hours',6,3,2,1)
,(123123,N'Hours',6,4,3,1)
,(123123,N'Hours',6,5,3,2)
,(123123,N'Hours',6,6,4,1)
,(123123,N'NI1',1,1,1,1)
,(123123,N'NI2',1,1,1,1)
,(123123,N'PAY',1,1,1,1)
,(123123,N'Teachers1',1,1,1,1)
,(123123,N'Teachers2',1,1,1,1)
,(123124,N'Hours',6,1,1,1)
,(123124,N'Hours',6,2,2,1)
,(123124,N'Hours',6,3,3,1)
,(123124,N'Hours',6,4,3,2)
,(123124,N'Hours',6,5,3,3)
,(123124,N'Hours',6,6,4,1)
,(123124,N'NI1',1,1,1,1)
,(123124,N'NI2',1,1,1,1)
,(123124,N'PAY',1,1,1,1)
--SELECT * FROM #Test2
Thanks for your help in advance.
The predicates of a CASE expression (i.e. what follows THEN and ELSE) always has to be a value, not another logical expression. Without seeing your entire query, I can suggest the following refactor:
WHERE
(a.Count = b.Count AND a.Rn1 = b.Rn1) OR
(a.Count <> b.Count AND a.Rn2 = b.Rn2 AND a.Rn3 = b.Rn3)
You're trying to use a CASE expression like a CASE statement. A CASE expression returns a scalar value not a boolean result. So, for example the following syntax is valid:
WHERE CASE [Column] WHEN 'A' THEN 1 ELSE 2 END = OtherColumn;
This is because the CASE expression returns the scalar value 1 or 2. The following statement, would be invalid:
WHERE CASE [Column] WHEN 'A' THEN OtherColumn = 1 ELSE OtherColumn = 2 END;
A CASE in a WHERE in T-SQL needs to be part of a boolean expression ({CASE expression} {=|!=|>|<|etc} {Expression}), not return one.
Using a CASE expression in a WHERE clause, however, makes your query non-SARGable, you are far better off righting out some boolean logic. For what yoiu have that would likely be:
WHERE (A.[Count] = b.[Count]
AND A.RN1 = B.RN1)
OR (A.[Count] != b.[Count]
AND A.RN2 = B.RN2
AND A.RN3 = B.RN3);
It's possible to do something like this with CASE expressions:
WHERE 1 = (CASE WHEN a.Count = b.Count THEN CASE WHEN a.Rn1 = b.Rn1 THEN 1 END
WHEN a.Count <> b.Count THEN CASE WHEN a.Rn2 = b.Rn2 and a.Rn3 = b.Rn3 THEN 1 END
END)
That's a literal translation of the original request without any optimization.
Related
How to use an INTEGER value to perform a lookup into a STRING array in a SQL SELECT statement and return the STRING
I have a SQL SELECT statement that returns an INTEFER in one of the columns of the result set. Instead of returning the INTEGER, I want to return a STRING from a constant ARRAY using the INTEGER in that column as the index into the ARRAY. For example, I have this SELECT statement that returns an INTEGER: SELECT iStatus FROM statusTable Instead of returning the INTEGER iStatus, I want to use the value within the SELECT statement to retrieve a STRING value from an ARRAY. DECLARE #list varchar (23) = 'APPLE, ORANGE, PEAR, OTHER' How would I modify the SELECT statement to return the STRING from #List indexed by iStatus? I have tried the CASE statement, and it works, but I have multiple STRING lookups I have to perform within the same SELECT statement and I would like to find a more elegant solution. I have tried this, and it does work, but I would like to reduce the number of lines in my SQL statement: SELECT StringStatus = CASE WHEN iStatus = 0 THEN 'Requested' WHEN iStatus = 1 THEN 'Pending' WHEN iStatus = 2 THEN 'Ordered' WHEN iStatus = 3 THEN 'Assigned' END
It goes without saying: storing this stuff in a table is the way to go. What if a new status is added? Do we do a repo-wide find/replace? That said, a more elegant way to write you CASE statement would be: CASE #iStatus WHEN 0 THEN 'Requested' WHEN 1 THEN 'Pending' WHEN 2 THEN 'Ordered' WHEN 3 THEN 'Assigned' END Then you have CHOOSE which is as close to an array as it gets in T-SQL. DECLARE #iStatus INT = 0 SELECT StringStatus = CHOOSE(#iStatus+1,'Requested','Pending','Ordered','Assigned') An even more elegant solution would be a scalar UDF provided that it's inlineable. CREATE OR ALTER FUNCTION dbo.StringStatus(#iStatus INT) RETURNS VARCHAR(12) WITH EXECUTE AS CALLER, SCHEMABINDING AS BEGIN RETURN( CASE #iStatus WHEN 0 THEN 'Requested' WHEN 1 THEN 'Pending' WHEN 2 THEN 'Ordered' WHEN 3 THEN 'Assigned' END); END; I say "inlineable" because otherwise the performance will be dreadful. A slightly less elegant (but faster) solution is to create an inline table valued function. CREATE OR ALTER FUNCTION dbo.StringStatus_itvf(#iStatus INT) RETURNS TABLE WITH SCHEMABINDING AS RETURN SELECT StringStatus = CASE #iStatus WHEN 0 THEN 'Requested' WHEN 1 THEN 'Pending' WHEN 2 THEN 'Ordered' WHEN 3 THEN 'Assigned' END; Storing this stuff in a table is the best option, otherwise the itvf solution is the way I would go. Here is each function in action: -- Sample Data DECLARE #things TABLE(N INT) INSERT #things VALUES(1),(0),(2),(1),(3); -- Scalar SELECT t.N, dbo.StringStatus(t.N) FROM #things AS t; -- iTVF SELECT t.N, dbo.StringStatus(t.N) FROM #things AS t CROSS APPLY dbo.StringStatus_itvf(t.N);
use defined CASE further in the same SELECT as variable
I have a SELECT statement in which I defined CASE expression which I'm using in another calculations within the same SELECT. However it's quite big CASE so I don't want to copy it all the time. Is it possible to define it as variable and then use only name of the variable in my calculations? E.g.: CASE WHEN xxxx END AS - define it as #test then use it for example in WHERE part - WHEN #test='X' is it possible? thanks
Yes you can AND doing so has great performance benefits. Consider these the following sample data and query. -- Sample data IF OBJECT_ID('tempdb..#sometable') IS NOT NULL DROP TABLE #sometable; CREATE TABLE #sometable(someid int identity, somevalue decimal(10,2)); INSERT #sometable(somevalue) VALUES (100),(1050),(5006),(111),(4); -- Query: DECLARE #var1 int = 100, #var2 int = 50, #var3 int = 900, #topPct tinyint = 90; SELECT TOP (#topPct) PERCENT someid, somevalue, someCalc = CASE WHEN #var3 < somevalue THEN (#var1/(#var2*2.00))+#var3 ELSE #var3+somevalue END, someRank = dense_rank() OVER (ORDER BY CASE WHEN #var3 < somevalue THEN (#var1/(#var2*2.00))+#var3 ELSE #var3+somevalue END) FROM #sometable WHERE CASE WHEN #var3 < somevalue THEN (#var1/(#var2*2.00))+#var3 ELSE #var3+somevalue END BETWEEN 900 AND 2000 ORDER BY -- simulate another event that causes a sort CASE WHEN #var3 < somevalue THEN (#var1/(#var2*2.00))+#var3 ELSE #var3+somevalue END; As mentioned, you could you CROSS APPLY like so: -- Improved SELECT TOP (#topPct) PERCENT s.someid, s.somevalue, someCalc=iTVF.result, someRank=DENSE_RANK() OVER (ORDER BY iTVF.result) FROM #sometable AS s CROSS APPLY (VALUES(CASE WHEN #var3 < somevalue THEN (#var1/(#var2*2.00))+#var3 ELSE #var3+somevalue END)) AS iTVF(result) WHERE iTVF.result BETWEEN 900 and 2000 ORDER BY iTVF.result; Notice how I have replaced all references of the CASE statement with itvf.result. The code is much cleaner. The other benefit is the execution plan. Using CROSS APPLY in this way prevents the optimizer from performing the same calculation twice.
You want something like that : select * from . . . cross apply ( values (case when . . . then . . . else . . . end) ) tt (test) where tt.test = 'X'
You can wrap your query inside a sub-query and place the SELECT and WHERE clauses outside it: SELECT a, b, c, test FROM ( SELECT a, b, c, CASE WHEN ... WHEN ... END AS test FROM ... ) AS sq WHERE sq.test = 'something' Ideally you should avoid case inside where clause.
Where Clause Return Null Record
I am not sure this is possible, but here is what I am trying to do: In the where clause below it is working fine but now I need to somehow: If #Contact has a value of 'Steve' but 'Steve' does not exist in contact I want to return records where contact is null. How can I achieve that? WHERE (contact = #Contact OR (contact IS NULL AND #Contact IS NULL))
One method uses a subquery. A version that doesn't use a subquery is: select top (1) with ties t.* from t where contact = #Contact or contact is null order by contact desc;
I'm guessing a bit on exactly what you are after, but one possibility might be something like this? select * from someTable t where 1 = (case when exists(select * from someTable t2 where t2.contact = #contact) then case when #contact = t.contact then 1 end else case when t.contact IS NULL then 1 end end) This means: IF at least one row exists in someTable, where someTable.contact = #contact, then return all rows where someTable.contact = #contact . Otherwise, return all rows where someTable.contact IS NULL.
If you set it up as a stored procedure, you can set the default to a blank and then use that in the WHERE statement. Below is a pseudo-example from a working proc. It uses a Full-Text Index but you get the idea. CREATE PROCEDURE [dbo].[procMyProc] #p_searchtermKeyword NVARCHAR(255) = '""' AS BEGIN SET NOCOUNT ON; SELECT * FROM MyTable WHERE ((CONTAINS(Keywords, #p_searchtermKeyword)) OR #p_searchtermKeyword = '""') END
T-SQL using 2 parameters and isNull
Could someone help me to understand the logic of this query (T-SQL in SQL Server 2014) in simple way? Select c.ContractID From dba.contract as c Inner Join dba.Person as r on (c.ContractID = r.ContractID and IsNull(isPrimary, 0) = 1) The part that I dont understand is the isNull(isPrimary, 0) = 1. What does that mean? Btw isPrimary is one of the columns in dba.person Thank you so much!
isNull(isPrimary, 0) = 1 isNull is a function of SQL which is used to verify null variable and above snippet describe as if isPrimary variable is null then replace this null value with 0. the purpose of this method to handle null pointer exception.
If you want to watch how it works, you can create table in your database: use [your_database_name]; create table dbo.test_table ( t int null ); insert into dbo.test_table values (0), (1), (2), (NULL); select t, isnull(t, 0) as function_result from dbo.test_table
Sample script for you to understand IsNull(isPrimary, 0) = 1 condition gives the result and helps in handling Null pointer exception. DECLARE #table AS TABLE (Id int, isPrimary varchar(20)) INSERT INTO #table SELECT 1,1001 UNION ALL SELECT 2,1002 UNION ALL SELECT 3,NULL UNION ALL SELECT 4,1004 SELECT Id,ISNULL(isPrimary,0) UIdnum FROM #table SELECT * FROM #table WHERE ISNULL(isPrimary,0)=1 SELECT * FROM #table WHERE ISNULL(isPrimary,0)=0
The IS_NULL function is only replacing the value of isPrimary to 0, in case the isPrimary is equal to NULL. Your check is only true when isPrimary is not null (because if it is, it will be replaced by 0) AND isPrimary = 1. SELECT c.contractid FROM dba.contract AS c INNER JOIN dba.person AS r ON (c.contractid = r.contractid AND isprimary = 1) WHERE isprimary IS NOT NULL
SQL return true if criteria match
I have an assignment table in my database. I also have a assignment_notes table, which has a reference to the assignment table. There can exists multiple rows in this table. When I select all my assignments, I want to check if there exists some notes to this assignment. And all I want is a true/false return. Is it possible to do something like (pseudo): Select all assignments; if assignment has assignment_notes HasNotes = true; else HasNotes = false. I hope I made this clear enough - I'm not so good at explaining programming stuff ;-)
DECLARE #Assignments TABLE ( Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY, Name VARCHAR(30) NOT NULL ) DECLARE #AssignmentNotes TABLE ( Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY, AssignmentId INT NOT NULL, Note VARCHAR(MAX) ) INSERT INTO #Assignments(Name) VALUES('Biology') INSERT INTO #Assignments(Name) VALUES('Chemistry') INSERT INTO #AssignmentNotes (AssignmentId, Note) VALUES(1, 'Studies on DNA') INSERT INTO #AssignmentNotes (AssignmentId, Note) VALUES(1, 'Evolution notes from Darwin') SELECT A.*, CASE WHEN COUNT(AN.Id) > 0 THEN 1 ELSE 0 END AS HasNotes FROM #Assignments AS A LEFT JOIN #AssignmentNotes AS AN ON A.Id = AN.AssignmentId GROUP BY A.Id, A.Name
Don't have SQL Server ready to test, but a query like this should work: SELECT A.*, CAST( CASE (SELECT TOP 1 AssignmentNotes_ID FROM AssignmentNotes AN WHERE AN.AssignmentID = A.AssignmentID) WHEN NULL THEN 0 ELSE 1 END AS BIT) AS HasNotes FROM Assignments A
SELECT a.*, (SELECT COUNT(*) FROM assignment_notes an WHERE an.assignmentid = a.id) as NumNotes FROM Assignment a That will give you the number of notes with that assignment.
You can translate your pseudo code to SQL if you use the case statement. On MSDN: http://msdn.microsoft.com/en-us/library/ms181765.aspx And another article just in case: http://www.devx.com/tips/Tip/15633
This approach means that you don't need a huge GROUP BY statement as a result of returning lots of Assignment fields. SELECT Assignment.*, CAST(CASE WHEN NotesQty>0 THEN 1 ELSE 0 END as bit) AS HasNotes FROM Assignment LEFT JOIN (SELECT AssignmentId,COUNT(*) AS NotesQty FROM assignment_notes GROUP BY AssignmentId) as Assignment_NotesQty ON Assignment_NotesQty.AssignmentId=Assignment.AssignmentId