Using IsNull() in a WHERE clause - sql

This query:
SELECT
sc.ContactID
, c.Price
, p.ParkID
FROM
tblc c
JOIN tblsc ON c.ID= sc.ID
LEFT JOIN tblp p ON sc.ID= p.ID
WHERE
(stuff = stuff)
AND (stuff = stuff)
Returns this:
ContactID LeadSalePrice ParkID
-------------------------------------
1 50 NULL
2 60 22
Now, I am trying to set up another AND clause for ParkID. But, I only want to execute this AND on rows where ParkID IS NOT NULL. So, if on rows where ParkID IS NULL, we want to always keep those rows. On rows where ParkID IS NOT NULL, we only want to keep those rows if ParkID matches the parameter.
If #ParkID = 22, then return both rows.
If #ParkID <> 22, then only return the top row.
If #ParkID = NULL, then return both rows.
I've tried this:
SELECT
sc.ContactID
, c.Price
, p.ParkID
FROM
tblc c
JOIN tblsc ON c.ID= sc.ID
LEFT JOIN tblp p ON sc.ID= p.ID
WHERE
(stuff = stuff)
AND (stuff = stuff)
AND p.ParkID =
CASE WHEN p.ParkID IS NULL THEN
NULL
ELSE
#ParkID
END
This doesn't work because the proper way to compare with null is not:
= NULL
It's
IS NULL
For the same reason (I assume), this doesn't work either:
AND p.ParkID = Isnull(p.ParkID, #ParkID)
How do I do this?

Simply check if p.ParkID is NULL or it matches #ParkID:
WHERE p.ParkID IS NULL
OR p.ParkID = #ParkID

More compact:
WHERE p.ParkID IN ('', NULL)

Related

Match specific or default value on multiple columns

raw_data :
name
account_id
type
element_id
cost
First
1
type1
element1
0.1
Second
2
type2
element2
0.2
First
11
type2
element11
0.11
components:
name
account_id (default = -1)
type (default = null)
element_id (default = null)
cost
First
-1
null
null
0.1
Second
2
type2
null
0.2
First
11
type2
element11
0.11
I seek to check whether the cost logged in raw_data is the same as that in components for a given combination. They need to be joined on column name.
Remaining fields in raw_data are always populated. In components, any row can be a combination of specific values and the default values.
I seek to match the columns from raw_data to components wherever I find a match and otherwise need to use the default value to get the cost.
I failed with left join and union and IN.
E.g. For the first row in raw_data table with name "First", I do not have account_id = 1 in the components table. So I need to go with account_id = -1.
Match as many specific values as found in components, Otherwise resort to default values.
I think one way you could do this is something like:
SELECT *
FROM
(
SELECT rd.name, rd.account_id, rd.type, rd.element_id, rd.cost raw_cost, c.account_id component_account_id, c.type component_type, c.element_id component_element_id, c.cost component_cost,
row_number() OVER (PARTITION BY rd.name, rd.account_id, rd.type, rd.element_id
ORDER BY
CASE WHEN c.account_id <> -1 THEN 1 END
+ CASE WHEN c.type IS NOT NULL THEN 1 END
+ CASE WHEN c.element_id IS NOT NULL THEN 1 END DESC) rd
FROM raw_data rd LEFT OUTER JOIN components c
ON rd.name = c.name
AND (rd.account_id = c.account_id or c.account_id = -1)
AND (rd.type = c.type OR c.type IS NULL)
AND (rd.element_id = c.element_id OR c.element_id IS NULL)
) iq
WHERE rd = 1
The idea here is to match on an actual match or the default. Then the row_number window function is used to prioritize the matches based on a count of how many columns actually matched (you said you don't care about ties, so this doesn't handle that). The outer query throws away the matches that aren't the best.
With the sample data above, this could be an inner join, but I left it as a left join since that's what was mentioned.
Here's a fiddle of it working. Hopefully this is close to what you want.
If the ratio of records in the tables is not 1 to 1, then an unambiguous sample cannot be made.
Also, if the selection condition is "Coincidence of at least one parameter", then it will also not work to make an unambiguous selection.
Below is an example that will bring you closer to solving the problem. It selects data that matches one of the selection criteria, however there may be duplicates!
Try this variant and report the result, possibly on a larger variant of samples and records
Maybe this will get you closer to the solution.
select rd.name, rd.account_id, rd.type, rd.element_id, rd.cost, c.cost
from raw_data rd
left join components c on rd.name = c.name
where (c.account_id = rd.account_id or c.account_id = -1) OR
(c.type = rd.type OR c.type is null) OR
(c.element_id = rd.element_id OR c.element_id = null)
You can build the priority of checking values through union
select rd.name, rd.account_id, rd.type, rd.element_id, rd.cost, c.cost
from raw_data rd
left join components c on rd.name = c.name
where c.account_id = rd.account_id and
c.type = rd.type
c.element_id = rd.element_id
union
select rd.name, rd.account_id, rd.type, rd.element_id, rd.cost, c.cost
from raw_data rd
left join components c on rd.name = c.name
where c.account_id = rd.account_id and
c.type = rd.type
union
select rd.name, rd.account_id, rd.type, rd.element_id, rd.cost, c.cost
from raw_data rd
join components c on rd.name = c.name
where c.account_id = rd.account_id
etc
Without seeing all the problems, all the data options in the tables, it is difficult to give the right solution, which may not be...

Outer join with mandatory chain

Imagine I have this data
people -> address changes -> address change reasons
I want details of all people
I also want to know if they have ever changed address because of fire. So I don't want to know what the reasons are but if they have changed address for 1 single reason.
Each person could possibly have multiple address change reasons
so I have
SELECT people.*
CASE WHEN add_change.reason_id is not NULL THEN
'Y'
ELSE
'N'
END as been_fire
from people
left outer join add_change ON person.id = add_change.person
left outer join add_change_reason ON add_change.reason_id = add_change_reason.id AND add_change_reason.text = 'FIRE'
but this returns multiple rows per person if they have many address changes.
I can't just use
left outer join add_change ON add_change.person = person.id AND add_change.reason_id = 5
as this isn't fixed data.
Use exists:
SELECT p.*,
(CASE WHEN EXISTS (SELECT 1
FROM add_change ac JOIN
add_change_reason acr
ON ac.reason_id = acr.id
NVL(address_change.reason,'N')
WHERE p.id = ac.person AND
acr.text = 'FIRE'
)
THEN 'Y' ELSE 'N'
END) as has_fire_address_change
from people p;
Note that this changes the flag to 'Y' and 'N', which is what the description of your problem suggests that you want.
You could do a left join to a derived table that only returns person IDs that do have the change reason 'FIRE':
SELECT p.*
CASE WHEN cr.person IS NOT NULL THEN 'Y' ELSE 'N' END as been_fire
from people
left join (
select ac.person
from add_change ac
where exists (select *
from add_change_reason acr
where acr.id = ac.reason_id
AND acr.text = 'FIRE')
) cr on cr.person = p.id

Fetch data from 3 three tables whichever is not null

I am trying to find the available address details from three tables.
When table 1 has the address details then get it from table1
If table 1 address has null value then consider table2,
If table 2 address has null value then consider table3, else display table1 address (null)
There are foreign keys in table 1 which can be used to join table 2 and 3 but they can be null as well in which case only data from table 1 to be considered.
In my query, I am able to join the tables when the foreign keys are available but in case they are null, the query doesn’t work!
I am not sure if I can add a ‘Case’ statement to ignore the ‘Join’ conditions in case the foreign keys are null.
Can someone please assist?
My Query is below :
SELECT donor.donor_num,
CASE
--WHEN donor.addr1 IS NULL THEN paraddress.addr1
--WHEN paraddress.addr1 IS NULL THEN enrparaddr.addr1
WHEN donor.addr1 IS NULL THEN enrparaddr.addr1
ELSE donor.addr1
END AS Address1,
CASE
--WHEN donor.addr2 IS NULL THEN paraddress.addr2
--WHEN paraddress.addr2 IS NULL THEN enrparaddr.addr2
WHEN donor.addr2 IS NULL THEN enrparaddr.addr2
ELSE donor.addr2
END AS Address2
FROM donor
JOIN enrparaddr ON enrparaddr.par_code = donor.enrol_code
--JOIN paraddress ON paraddress.par_code = donor.par_code
WHERE donor_num = '17206'
Please see attached image for the three tables
You can try below - using FULL OUTER JOIN & coalesce() function
SELECT donor.donor_num,
coalesce(donor.addr1,paraddress.addr1,enrparaddr.addr1) AS Address1,
coalesce(donor.addr2,paraddress.addr2,enrparaddr.addr2) AS Address2
FROM donor
FULL OUTER JOIN enrparaddr ON enrparaddr.par_code = donor.enrol_code
FULL OUTER JOIN paraddress ON paraddress.par_code = donor.par_code
WHERE donor_num = '17206'
SELECT donor.donor_num,CASE WHEN donor.addr1 IS NULL
THEN enrparaddr.addr1
ELSE donor.addr1
END AS Address1,
CASE WHEN donor.addr2 IS NULL
THEN enrparaddr.addr2
ELSE donor.addr2
END AS Address2
FROM donor
left join enrparaddr ON enrparaddr.par_code = donor.enrol_code
WHERE donor_num = '17206'
I would recommend:
SELECT d.donor_num,
coalesce(d.addr1, pp.addr1, epe.addr1) AS Address1,
coalesce(d.addr2, pp.addr2, epe.addr2) AS Address2
FROM donor d LEFT JOIN
paraddress pp
ON pp.par_code = d.par_code LEFT JOIN
enrparaddr epe
ON epe.par_code = d.enrol_code AND
pp.par_code IS NULL
WHERE d.donor_num = 17206 -- do not use single quotes for numbers
Notes:
You want a LEFT JOIN because you want all rows in donors.
You should be joining the tables in the order used for the COALESCE().
The second JOIN condition can be limited to cases where the first does not match.
Number constants should not use single quotes.
You can do this with simple Case .. When Statement:
Select Case
When A.ID is NULL And B.ID is NULL And C.ID is NULL Then -- when all columns contain null
NULL
When A.ID is NULL And B.ID is NULL Then
C.ID
When B.ID is NULL and C.ID is NULL Then
A.ID
When A.ID is NULL And C.ID is NULL Then
B.ID
End As ID
From A, B, C

Translation of a character to another SQL Server

I want to translate a character from A - B but also in the same query I want to translate B - C if is found on a list. Let's say we have word "Apple" that gets translated to "Orange" but "Orange" it is also on the list and it gets translated to "Coconut", so the final result would be "Coconut". Is this possible ?. I do not want to use a cursor but i just can't find the answer..
update tableA
set Value = b.TargetValue
from tableA a
join
tableB b on b.SourceValue = a.Value
from my TableA i have let's say a list of fruits for this example i just have the fruit "Apple" on tableA but in tableB i have a translation for that word to "Orange", but also in the same tableB i have a translation for "Orange" to "Coconut" so i would expect to have as final result "Coconut". Does that help? it's my first time sorry if i didn't explain well.
EDIT
I have created a function for this. Hope it helps someone else with the same problem.
CREATE FUNCTION [dbo].[FunctionName]
(
#sourceValue varchar(11)
)
RETURNS varchar(11)
AS
BEGIN
declare #targetId varchar(11) = (select TargetID from tableWithValues where
SourceID = #sourceValue)
if #targetId is not null and #targetId <> #sourceValue
begin
set #targetId = dbo.FunctionName(#targetId)
end
else
begin
return #sourceValue
end
return #targetId
end
I would suggest you get a final dataset by joining both fruit tables, so when you get your final fruit just join that dataset(cte or temp table) with the table you want to update.
Hope this approach helps you solve the problem.
I don't think that there is general solution for this, but there is a work-around if there are reasonable limits to the number of substitutions.
For example, the A => B => C you describe has two levels of substitution. If the max number of levels is e.g. 5 you can code like this:
update tableA
set Value = case when b5.TargetValue is not null then b5.targetValue
else when b4.TargetValue is not null then b4.TargetValue
else when b3.TargetValue is not null then b3.TargetValue
else when b2.TargetValue is not null then b2.TargetValue
else when b1.TargetValue is not null then b1.TargetValue
else b0.TargetValue end case
from tableA a
join
tableB b0 on b0.SourceValue = a.Value
left outer join tableB b1 -- outer join for no sub
on b1.SourceValue = b0.TargetValue
left outer join tableB b1
on b2.SourceValue = b1.TargetValue
left outer join tableB b1
on b3.SourceValue = b2.TargetValue
left outer join tableB b1
on b4.SourceValue = b3.TargetValue
left outer join tableB b1
on b5.SourceValue = b4.TargetValue
Here, 5 levels are supported: A =>B =>C =>D =>E =>F. If you have a situation where 6 levels are needed (e.g F => G) then it won't happen, and the result will be F.
Note that the order of the when bx.TargetValue is not null statements is important.

Not equal Null not working in sql server

I have a query given below
SELECT
B.[Date],
B.[Detail],
B.[Number],
B.[Total],
CASE WHEN B.[ApId] != null THEN Ap.Name ELSE B.[Name]END Name,
FROM [Bacs] B
LEFT JOIN Table ap ON b.ApId= ap.Id
In the Case When query I am trying to return the Name based on the b.ApId value.
But even if b.ApId is not null I am getting B.Name instead of Ap.Name.
Can anyone point out where I am going wrong.
!= will evaluate for values, while NULL is not a value.
So you have to either use IS NULL or IS NOT NULL to compare nulls.
SELECT
B.[Date],
B.[Detail],
B.[Number],
B.[Total],
CASE WHEN B.[ApId] is not null THEN Ap.Name ELSE B.[Name]END Name,
FROM [Bacs] B
LEFT JOIN Table ap ON b.ApId= ap.Id
Use IS NOT NULL, IS NULL to compare with NULL value, Or use ISNULL, COALESCE function
SELECT
B.[Date],
B.[Detail],
B.[Number],
B.[Total],
CASE WHEN B.[ApId] IS NOT NULL THEN Ap.Name ELSE B.[Name] END Name
-- ISNULL(Ap.Name, B.[Name]) as Name
-- COALESCE(Ap.Name, B.[Name],'') as Name
FROM [Bacs] B
LEFT JOIN Table ap ON b.ApId= ap.Id