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.
I have 3 different outcomes of CTE that I need to LEFT JOIN each other:
Main Table #Policies contains all VehiclePolicyLimitsID values:
#LiabilityPremium:
#HiredPremium:
As an example I mimic the results of CTE's into 3 table variables:
declare #Policies table (VehiclePolicyLimitsID int)
insert into #Policies values (2101891),
(2101892),
(2101893),
(2101894),
(2119235),
(2119236),
(2119237),
(2119238),
(2190860),
(2190861),
(2190862),
(2190863)
--select * from #Policies
declare #LiabilityPremium table (Quoteid int, ClassCode int, VehiclePolicyLimitsID int, LiabilityPremium money)
insert into #LiabilityPremium values (728436,3199,2101892,1723),
(728436, 23199,2101893,1855),
(728436,68199,2101894,133),
(741626,3199,2119236,0),
(741626,23199,2119237,0),
(741626,68199,2119238,0),
(774168,3199,2190861,0),
(774168,23199,2190862,0),
(774168,68199,2190863,0)
--select * from #LiabilityPremium
declare #HiredPremium table (Quoteid int, ClassCode int, VehiclePolicyLimitsID int, LiabilityPremium money)
insert into #HiredPremium values ( 728436, NULL, 2101891, 25),
(741626, NULL, 2119235, 0),
(774168, NULL, 2190860, 0)
--select * from #HiredPremium
select
COALESCE(l.Quoteid,h.QuoteID,'') as QuoteID,
COALESCE(l.ClassCode,h.ClassCode,'') as ClassCode,
COALESCE(l.VehiclePolicyLimitsID,h.VehiclePolicyLimitsID,'') as VehiclePolicyLimitsID,
l.LiabilityPremium + h.LiabilityPremium as LiabilityPremium
from #Policies p
left join #LiabilityPremium l ON l.VehiclePolicyLimitsID = p.VehiclePolicyLimitsID
left join #HiredPremium h ON h.VehiclePolicyLimitsID = p.VehiclePolicyLimitsID
But for some reason the outcome of LiabilityPremium is all NULL's:
I would expect the result looks like this with total LiabilityPremium = $3,736
Is any way to join somehow to receive desirable result?
That is because null on either side of the addition operator will yield a result of null. You can use ISNULL(LiabilityPremium, 0) Example:
ISNULL(l.LiabilityPremium,0) + ISNULL(h.LiabilityPremium,0) as LiabilityPremium
or you can use COALESCE instead of ISNULL.
COALESCE(l.LiabilityPremium,0) + COALESCE(h.LiabilityPremium,0) as LiabilityPremium
Edit
I am not sure if this is coincidence with this small data set or expected but if it is always expected that either #LiabilityPremium.LiabilityPremium or #HiredPremium.LiabilityPremium will always be null then there is no need to perform addition. Instead use COALESCE directly on those 2 columns.
COALESCE(l.LiabilityPremium, h.LiabilityPremium) as LiabilityPremium
COALESCE(l.LiabilityPremium,0) + COALESCE(h.LiabilityPremium,0) as LiabilityPremium
That's because of
l.LiabilityPremium + h.LiabilityPremium
If any of the two is NULL, the expression is NULL.
This expression should fix it
COALESCE(l.LiabilityPremium, 0.00) + COALESCE(h.LiabilityPremium, 0.00)
I'm writing a stored procedure with quite a lot of expensive work to do that may or may not take a filter parameter. Doing the filtering is itself quite expensive, and the table being filtered is large. I just tried to change the inner filtering function so throw an error if called with invalid parameters, as a warning to developers not to use it that way.
BUT - If I call my outer test function with NULL, it works as I'd expect, not calling the inner function and not throwing the error. If I call my outer test function with a variable with the VALUE of NULL, then it calls the filter function with a null parameter, and throws the error, even thought the code only says to call the function when the value is not null.
What's going on here?
Much simplified example:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MyTable]') AND type in (N'U')) DROP TABLE MyTable
GO
CREATE TABLE MyTable (Pk int, Field int)
GO
INSERT INTO MyTable VALUES (1, 1)
INSERT INTO MyTable VALUES (2, 4)
INSERT INTO MyTable VALUES (3, 9)
INSERT INTO MyTable VALUES (4, 16)
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[FilterRows]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) DROP FUNCTION FilterRows
GO
CREATE FUNCTION FilterRows(#searchParameter int)
RETURNS #Pks TABLE
(
Pk int
)
AS
BEGIN
IF (#searchParameter IS null)
BEGIN
-- This is bad news. We don't want to be here with a null search, as the only thing we can do is return every row in the whole table
-- RAISERROR ('Avoid calling FilterRows with no search parameter', 16, 1)
-- we can't raise errors in functions!
-- Make it divide by zero instead then
INSERT INTO #Pks SELECT Pk FROM MyTable WHERE 1/0 = 1
END
ELSE
BEGIN
INSERT INTO #Pks SELECT Pk FROM MyTable WHERE Field > #searchParameter
END
RETURN
END
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[OuterFunction]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) DROP FUNCTION OuterFunction
GO
CREATE FUNCTION OuterFunction(#searchParameter int)
RETURNS TABLE AS
RETURN
SELECT *
FROM
MyTable
WHERE
(#SearchParameter IS NULL) OR (#searchParameter IS NOT NULL AND Pk IN (SELECT Pk FROM dbo.FilterRows(#searchParameter)))
GO
SELECT * FROM dbo.OuterFunction(2) -- Returns filtered values
SELECT * FROM dbo.OuterFunction(null) -- returns everything, doesn't call FilterRows
DECLARE #x int = null
SELECT * FROM dbo.OuterFunction(#x) -- WTF! Throws error!
The difference when a value null is passed than when constant null is passed is the same difference between using (is Null) and (= null)
#var = null -- considered as false
#var is null -- considered as unknown
for more details : SQL is null and = null
so if you want to make behavior of both (calling constant null & pass Null value) is the same, use the following tricky although I don't prefer this one.
Alter FilterRows function to be
IF (#searchParameter = null)
--IF (#searchParameter is null)
Note: sorry for typing this answer here, it is supposed to be comment instead of answer, the rule is "You must have 50 reputation to comment" and I have only 22 :(
I think what's going on is that in
SELECT * FROM MyTable WHERE (#SearchParameter IS NULL) OR
(#searchParameter IS NOT NULL AND Pk IN (SELECT Pk FROM dbo.FilterRows(#searchParameter)))
The query analyzer can see that the subquery
(SELECT Pk FROM dbo.FilterRows(#searchParameter))
does not depend on any values from MyTable. As it's constant for all rows, it runs that subquery first, in order to join MyTable to the results. So it executes it before evaluating the WHERE clause where it tests whether #searchParameter IS NULL or not.
When #searchParameter is just "NULL" and not a variable with value NULL, then the analyzer can short-circuit the whole where clause in the execution plan and so knows not to pre-calculate the subquery.
Or, something like that.
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