SQL left join issue with null values - sql

I inherited a query that uses left joins. One of the things the query is doing is removing any archived records, that is where archived = 'Y'. This is how the query looks:
select P.firstname, E.entityid, C.committeeid, L.locationname
from Pract P
left join Committee C
on P.commiteeid = C.committeeid
and c.archived = 'N'
left join Entity E
on P.entityid = E.EntityID
and e.archived = 'N'
left join Location L
on E.location = L.location
and l.archived = 'N'
The result should only return records where archived <> 'Y'. I think a problem with putting the filter with the "on" is that it will return a record where c.archived = 'N' and just put a null in the archived field, which is not correct:
FirstName EntityID CommitteeId
John 55 null
If c.archived = 'Y' then the record should not show up.
I believe the archived filter should be in the where clause, like this:
select firstname, entityid, committeeid
from Pract P
left join Committee C
on P.commiteeid = C.committeeid
left join Entity E
on P.entityid = E.entityid
left join Location L
on E.Locationid = L.locationid
where c.archived = 'N'
and e.archived = 'N'
and l.archived = 'N'
The problem I'm finding is that there are instances where the archived field from Committee is null(it's not a 'Y' or an 'N'). Using my solution incorrectly eliminates the records since null <> 'N.'
If I try this:
where c.archived <> 'Y'
it does not work, I'm guessing because NULL does not evaluate to anything.
If I try this:
where (c.archived = 'N' or c.archived is null)
it doesn't work as it now brings back those null records caused by the left join. I can't replace the left join with an inner join because that will exclude records where c.committeeid is null.
I just want to bring back records where archived <> 'Y', which includes those where the field is null.
To be clear, this is what the records in the table can look like:
FirstName EntityID Archived
John 55 Y
Tom 56 NULL
Rob 57 N
In this instance I want the returned records to look like:
Tom 56 NULL
Rob 57 N
John would be eliminated because Archived = 'Y.'
Is there another way to do this?

If I understand correctly, you want to use another field in the where clause for the filtering:
select firstname, entityid, committeeid -- this will return an error on committeeid
from Pract P left join
Committee C
on P.commiteeid = C.committeeid and
(c.archived <> 'Y' or c.archived is null)
where c.committeeid is not null;
You may also be able to do this with exists more easily:
select p.*
from Pract P
where not exists (select 1
from Committee C
where P.commiteeid = C.committeeid and
c.archived = 'Y'
);
This handles the NULL values automagically.

I believe that you are looking for this version. You just start with what you had and modify joining condition to cover values that are not equal to Y and that are NULL.
select firstname, entityid, committeeid
from Pract P
left join Committee C
on P.commiteeid = C.committeeid
and (c.archived <> 'Y' OR c.archived IS NULL)
Then if you need only items that has record in commitee table. you put it in WHERE clause.
WHERE P.commiteeeid = C.commiteeid
But it will just emulate INNER JOIN.
Your problem with comparing NULL is that you can't use <> to test for NULL values. If you want records with NULL values you have to test for IS NULL explicitly.
So your updated query would look like:
select P.firstname, E.entityid, C.committeeid, L.locationname
from Pract P
left join Committee C
on P.commiteeid = C.committeeid
and (c.archived <> 'Y' OR c.archived IS NULL)
left join Entity E
on P.entityid = E.EntityID
and (e.archived <> 'Y' OR e.archived IS NULL)
left join Location L
on E.location = L.location
and (l.archived <> 'Y' OR l.archived IS NULL)
And if you have only values 'Y' 'N' and NULL, then you can also use
and (c.archived = 'N' OR c.archived IS NULL)
Just to be sure we understand each other here is minimal example for your query
create table Pract (firstname varchar(20), cid int)
create table comt ( cid int, archived varchar(1) )
insert into Pract values ('Tom',1),('Adam',2),('Mark',3),('Bob',4)
insert into comt values (1,'Y'), (2,'N'), (3,NULL)
--
select firstname, P.cid, C.cid, C.archived
from Pract P
LEFT join comt C
on P.cid = C.cid
and (c.archived <> 'Y' OR c.archived IS NULL)
With result
firstname cid cid archived
1 Tom 1 NULL NULL
2 Adam 2 2 N
3 Mark 3 3 NULL
4 Bob 4 NULL NULL
Or with INNER JOIN
select firstname, P.cid, C.cid, C.archived
from Pract P
INNER join comt C
on P.cid = C.cid
and (c.archived <> 'Y' OR c.archived IS NULL)
With result
firstname cid cid archived
1 Adam 2 2 N
2 Mark 3 3 NULL

The solution depends on how you want to handle the NULL values from the left join. If NULL means not archived then
select firstname, entityid, committeeid
from Pract P
left join Committee C
on P.commiteeid = C.committeeid
left join Entity E
on P.entityid = E.entityid
left join Location L
on E.Locationid = L.locationid
where (c.archived = 'N' OR c.archived IS null)
and (e.archived = 'N' OR e.archived IS null)
and (l.archived = 'N' OR l.archived IS NULL)

Related

oracle outer join with condition in where clause

I have a case table and case_status which is a child table. Earlier case_status was a mandatory field but not anymore.
So i had to change from join to left outer join. After adding this join condition the records are not getting picked up when case_status table doesnt have a record for a case.
case_status has a lookup to lkp_case_status hence having an outer join even there.
My query looks like this
select c.* from cases c
left outer join (SELECT case_id,
case_status_id,
COUNT(DECODE(case_status_id, 16, 1, NULL)) OVER (PARTITION BY case_id) AS IS_CLOSED
FROM case_status) cs ON cs.case_id = c.case_id
left outer join lkp_case_status lkp_cs
on lkp_cs.id = cs.case_status_id
where c.case_type = 'P'
and c.delete_date is null
AND cs.is_closed = 0;
I think its the cs.is_closed = 0 is not letting the record to get pulled up.
So i added AND cs.is_closed = 0; within the join condition as below,
select c.* from cases c
left outer join (SELECT case_id,
case_status_id,
COUNT(DECODE(case_status_id, 16, 1, NULL)) OVER (PARTITION BY case_id) AS IS_CLOSED
FROM case_status) cs ON cs.case_id = c.case_id AND cs.is_closed = 0
left outer join lkp_case_status lkp_cs
on lkp_cs.id = cs.case_status_id
where c.case_type = 'P'
and c.delete_date is null;
But this query is pulling all the records irrespective of the decode condition in the 3rd line.
3rd line - if case_status = 16, which means case is closed and dont pull the record.
Any inputs on how to make it work. Basically it has to pull all the records from cases table , if caseStatus table has a value of case_status = 16 dont pull the case record too.
case
id name
1 AAA
2 BBB
3 CCC
case_status
1 16
2 1
Output should show as follows:
Result:
id name
2 BBB
3 CCC
But what am getting is
id name
2 BBB
My query is not picking case with id = 3.
Any help is highly appreciated. Thank you.
If you want to add a filtering predicate for an outer-joined table or table expression you'll need to account for their columns to show up as nulls. You can do:
select c.* from cases c
left outer join (SELECT case_id,
case_status_id,
COUNT(DECODE(case_status_id, 16, 1, NULL))
OVER (PARTITION BY case_id) AS IS_CLOSED
FROM case_status) cs ON cs.case_id = c.case_id
left outer join lkp_case_status lkp_cs
on lkp_cs.id = cs.case_status_id
where c.case_type = 'P'
and c.delete_date is null
AND (cs.is_closed = 0 or cs.is_closed is null); -- changed here

LEFT OUTER JOIN WITH inner join with case in SQL DBMS

I have the following query
SELECT
A.WORKNR AS "WORK-nr",
UCASE(A.NAME) AS "NAME" ,
UCASE(A.ADRESS) AS "adress",
A.ZIPNR,
UCASE(A.ZIPADR) AS "zipadress",
A.NNR,
CASE
WHEN AO.ORG_REF IS NULL THEN 'OK'
ELSE 'NO '
END AS RESULT OF ORG ,
CASE
WHEN B.B = 1 THEN 'OK'
ELSE 'COME WITH ANOTHER NNR'
END AS COMPARE_RESULT
FROM
WORK.WORK_ADRESS A
LEFT JOIN
(SELECT
POSTNR, UCASE(STREET) AS STREET,
COUNT(DISTINCT NNR) AS B
FROM
WORK.WORK_ADRESS
GROUP BY
ZIPNR, UCASE(STREET)) B ON B.STREET = UCASE(A.STREET)
AND B.ZIPNR = ZIPNR
LEFT JOIN
(SELECT *
FROM WORK.ORGANISATION2) AO ON AO.WORK_REF = A.WORK_REF
AND AO.TILL >= CURRENT DATE
what I want is to add a JOIN to a third table ORG3 that contains the name for org genom
SELECT WORK.ORGANISATION2
INNER JOIN STYR.ORG2 ORG3 ON AO.ORG_REF3 = ORG3.ORG_REF
and get the column OR3.NAME as a separate column.
Can anyone help me? I am thankful

Select an ID where there is only one row and that row is a specific value

I have this query. There's a lot of joins because I am checking if an ID is linked to any of those tables.
Currently, this query shows me any ID's that are not linked to any of those tables. I would like to add to it so that it also shows any IDs that are linked to the d table, but only if there is only 1 row in the D table and the type in the D field is 'member'.
SELECT
c.ID,
c.location,
c.pb,
c.name,
c.surname
FROM c
LEFT JOIN l on c.rowno = l.rowno
LEFT JOIN d on c.rowno = d.rowno
LEFT JOIN t on c.rowno = t.rowno
LEFT JOIN cj ON (c.rowno = cj.rowno OR c.rowno = cj.rowno2)
LEFT JOIN dj ON c.rowno = d.rowno
LEFT JOIN lg ON c.rowno = lg.rowno
LEFT JOIN tj ON c.rowno = tj.rowno
WHERE
c.status != 'closed'
AND l.rowno IS NULL
AND d.rowno IS NULL
AND t.rowno IS NULL
AND cj.rowno IS NULL
AND dj.rowno IS NULL
AND lg.rowno IS NULL
AND tj.rowno IS NULL
My first thought is to just add
WHERE D.type = 'member'
But that gives me all IDs that have a row with D.type = member (they could have 10 rows with all different types, but as long as 1 of those has type = member it shows up). I want to see ID's that ONLY have d.type = member
I'm sorry if I'm wording this badly, I'm having trouble getting this straight in my head. Any help is appreciated!
I would use exists for all conditions except the one on the D table:
SELECT c.*
FROM c JOIN
(SELECT d.rownum, COUNT(*) as cnt,
SUM(CASE WHEN d.type = 'Member' THEN 1 ELSE 0 END) as num_members
FROM t
GROUP BY d.rownum
) d
ON c.rownum = d.rownum
WHERE c.status <> 'closed' AND
NOT EXISTS (SELECT 1 FROM t WHERE c.rowno = t.rowno) AND
NOT EXISTS (SELECT 1 FROM l WHERE c.rowno = l.rowno) AND
. . .
I find NOT EXISTS is easier to follow logically. I don't think there is a big performance difference between the two methods in SQL Server.

How do I go about checking the existence of records in all tables JOIN`ed in a statement?

I want ensure none of the tables included through INNER JOIN return 0 rows, resulting in an empty result-set due to the join being INNER. I want to be able to return information to the user about which table had no records, causing the query to return empty.
PS: Please ignore Foreign Keys for this question.
Example-query:
SELECT * FROM Person P
INNER JOIN [User] U ON U.PersonId = P.Id
INNER JOIN Email E ON E.UserId = U.Id
INNER JOIN Company C on C.Id = U.CompanyId
WHERE P.Id = 3
Let's say the user had no associated Email-records. I then want to be able to tell the user about this. Note: I only need to print the first failing step, I.E if the user has no Email and no Company, I only need to tell the user about him not having any Email.
How I would have solved it earlier:
I would perform the complete query in steps, building it up join by join, doing a whole lot of redundant querying. I really don't like this solution at all, which is why I'm asking for help.
-- Ensure Person exists
IF NOT EXISTS (
SELECT * FROM Person P
WHERE P.Id = 3
)
BEGIN
PRINT 'No associated Person was found';
RETURN 1;
END
-- Ensure User exists
IF NOT EXISTS (
SELECT * FROM Person P
INNER JOIN [User] U ON U.PersonId = P.Id
WHERE P.Id = 3
)
BEGIN
PRINT 'No associated User was found';
RETURN 1;
END
And so on. How can I write this more concise, in a way that solves the same problem but avoids repeating queries?
Thanks in advance.
Update
By looking at the answers, I realized my example was bad. Using LEFT JOIN is ok in this example, since the "join-chain" is not straight; Person joins User, then User joins in multiple directions, like this:
Person - User - Email
|
Company
I'll try to provide a different example:
SELECT * FROM a
INNER JOIN b ON b.a_id = a.id
INNER JOIN c ON c.b_id = b.id
INNER JOIN d ON d.c_id = c.id
INNER JOIN e ON e.d_id = d.id
INNER JOIN f ON f.e_id = e.id
INNER JOIN g ON g.f_id = f.id
The join-chain would then look like this:
a - b - c - d - e - f - g
If I were to use LEFT JOIN's to ensure existence of records, the IIF/CASE statements would get pretty terrible. Example:
SELECT
CASE WHEN b.id is null THEN 'b is null' END as b_is_null
CASE WHEN b.id is not null and c.id is null THEN 'b is null' END as c_is_null
..
CASE WHEN b.id is not null and c.id is not null and d.id is not null and e.id is not null and f.id is not null and g.id is null THEN 'g is null' AS g_is_null
FROM a
LEFT JOIN b ON b.a_id = a.id
LEFT JOIN c ON c.b_id = b.id
LEFT JOIN d ON d.c_id = c.id
LEFT JOIN e ON e.d_id = d.id
LEFT JOIN f ON f.e_id = e.id
LEFT JOIN g ON g.f_id = f.id
It can get really ugly. And these examples are with 1-character alias-names and 2-character property-names.
Keep in mind, I also want to check if the first table (Person/a, the one not joined) also returns rows.
You need to use left join. From your query, if a person doesn't have any user they obviously doesn't have any email or company. So all left join will be fine.
SELECT P.*,
IIF(U.PersonID IS NULL, 0, 1) AS isUserExists,
IIF(E.UserId IS NULL, 0, 1) AS isEmailExists,
IIF(C.Id IS NULL, 0, 1) AS isCompanyInformationExists
FROM Person P
LEFT JOIN [User] U ON U.PersonId = P.Id
LEFT JOIN Email E ON E.UserId = U.Id
LEFT JOIN Company C on C.Id = U.CompanyId
WHERE P.Id = 3
If U.PersonID IS NULL then no user existed, if E.UserId IS NULL then no email existed, if C.Id IS NULL then no company information existed for that person. By this you can use your messages as you need.
Use case statements like below and print your message.
select
case when u.PersonId is null and p.id is not null then 'user not exists'
WHEN u.PersonId IS NOT NULL AND p.id IS NOT NULL AND e.mailid is null THEN 'mail id not exist'
else 'No associated Person was found' end message
FROM Person P
left JOIN [User] U ON U.PersonId = P.Id
left JOIN Email E ON E.UserId = U.Id
left JOIN Company C on C.Id = U.CompanyId
WHERE P.Id = 3
DECLARE #Email VARCHAR(50)
DECLARE #Company VARCHAR(50)
SELECT #Email = E.Email, #Company = C.CompanyName FROM Person P
INNER JOIN [User] U ON U.PersonId = P.Id
LEFT JOIN Email E ON E.UserId = U.Id
LEFT JOIN Company C on C.Id = U.CompanyId
WHERE P.Id = 3
IF #Email IS NULL
Print 'NO Email'
IF #Company IS NULL
Print 'No Company'

adding a where condition for one criteria in sql query

I need to add a where condition for a SQL query , means when the dept id become 9 only i need that where condition , else i dont required.
i have written the below query , is the approach is correct?
SELECT
b.DeptId,
b.DeptName,
a.SurveyID,
a.SurveyName,
a.Status,
a.AllUsers,
IsNull(a.SelectedUsers,'') as SelectedUsers,
a.OpenDate,
a.CloseDate,
e.Role as RoleName
from Surveys a
inner join Departments b
on a.deptid=b.deptid
left outer join
surveyUsers c
on c.surveyid=a.SurveyID
and c.empCode= 9902
left outer join [360HRSurveyEmployee] d
on d.surveyid=a.SurveyID
left outer join [360HRSurvey] e
on e.sempid = c.empCode
and e.empid = d.empid
where ( c.empCode= 9902 or a.AllUsers = 1 )
and a.status in (1)
and a.OpenDate <= '6/9/2015'
and a.CloseDate >= '6/9/2015'
and CASE WHEN DeptId == 9
THEN e.Role IS NOT NULL END
order by b.DeptID,a.SurveyID
Note the last three lines in the above query where i added the case :
and CASE WHEN DeptId == 9
THEN e.Role IS NOT NULL END
order by b.DeptID,a.SurveyID
I am getting a syntax error also
Incorrect syntax near '='.
If I understand you correctly, you only need rows where DeptId is not 9, or DeptId is not null. Also, your gross disregard for consistency in your capitalization hurts me. What is this beast?!
SELECT
b.DeptID, b.DeptName,
a.SurveyID, a.SurveyName, a.Status, a.AllUsers,
ISNULL(a.SelectedUsers,'') as SelectedUsers,
a.OpenDate, a.CloseDate, e.Role as RoleName
FROM
Surveys AS a
INNER JOIN Departments AS b
ON a.DeptID = b.DeptID
LEFT OUTER JOIN SurveyUsers AS c
ON (c.SurveyID = a.SurveyID AND c.EmpCode = 9902)
LEFT OUTER JOIN [360HRSurveyEmployee] AS d
ON d.SurveyID = a.SurveyID
LEFT OUTER JOIN [360HRSurvey] AS e
ON (e.EmpID = c.EmpCode AND e.EmpID = d.EmpID)
WHERE
(
c.EmpCode = 9902
OR a.AllUsers = 1
)
AND a.Status = 1
AND a.OpenDate <= '6/9/2015'
AND a.CloseDate >= '6/9/2015'
AND (
a.DeptID != 9
OR e.Role IS NOT NULL
)
ORDER BY
a.DeptID,
a.SurveyID;
and CASE WHEN DeptId == 9
You only need one = sign for this comparison.
You need to use single =
CASE WHEN DeptId = 9
THEN e.Role END
What exactly do you want to achive with this line? THEN e.Role IS NOT NULL END