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

Resources