Select from column including null values - sql

I'm trying to create SELECT that includes various foreign key to list all the rows it has, but there is two foreign key that can be null.
bank_id and bbank_id can be null, so with this example it only will list rows that doesn't have null values in bank_id and bbank_id, but I need it to return rows where bank_id and bbank_id are null or not null.
I tried using IS NOT NULL AND NULL, well it doesn't work.
SELECT E.emp_id, E.emp_name, E.emp_surname1, E.emp_surname2,
J.job_name, U.ubi_name, D.dep_name, POR.por_type,
B.bank_name, BB.bbank_name
FROM public.employee E, public.ubi U,
public.jobs J, public.department D,
public.percenttable POR, public.bank B,
public.bbank BB
WHERE E.ubi_id = U.ubi_id AND E.job_id = J.job_id
AND E.dep_id = D.dep_id AND E.por_id = POR.por_id
AND E.bank_id = B.bank_id AND E.bbank_id = BB.bbank_id
AND E.ubi_id IS NOT NULL AND E.job_id IS NOT NULL AND E.dep_id IS NOT NULL
AND E.por_id IS NOT NULL AND E.bank_id IS NOT NULL AND E.bbank_id IS NOT NULL
ORDER BY E.emp_id ASC;
I read something about using LEFT JOIN but no idea how to implement it here.

You need to use modern JOIN syntax defined in the SQL-92 standard, 27 years ago. Here's how your query should look using left outer joins:
select
e.emp_id, e.emp_name, e.emp_surname1, e.emp_surname2,
j.job_name, u.ubi_name, d.dep_name, por.por_type,
b.bank_name, bb.bbank_name
from public.employee e
left join public.ubi u on e.ubi_id = u.ubi_id
left join public.jobs j on e.job_id = j.job_id
left join public.department d on e.dep_id = d.dep_id
left join public.percent por on e.por_id = por.por_id
left join public.bank b on e.bank_id = b.bank_id
left join public.bbank bb on e.bbank_id = bb.bbank_id
where e.ubi_id is not null
and e.job_id is not null
and e.dep_id is not null
and e.por_id is not null
-- and e.bank_id is not null -- removed per your requirement
-- and e.bbank_id is not null -- removed per your requirement
order by e.emp_id asc
Welcome to the 21st century! ;-)

Related

SQL left join issue with null values

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)

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'

CTE Missing Records

The first query listed below returns some logistical data associated with hires that have been made within a particular period of time. The query returns 478 records.
SELECT c.candidate_id AS candidate_id
,o.name
,j.name AS job_title
,c.applied_from
,job_id AS job_id
,cjs.score AS smart_rank_score
,cjs.is_completed AS smartrank_completion_status
,c.hired_at
FROM candidate_jobs c
LEFT JOIN organizations o ON o.id = c.organization_id
LEFT JOIN candidate_job_surveys cjs ON cjs.candidate_job_id = c.id
LEFT JOIN jobs j ON j.id = c.job_id
WHERE o.name LIKE ANY ('{"%Tutor Doctor%"}')
AND c.hired_at :: date BETWEEN '2015-01-01' AND '2016-02-22'
ORDER BY 8 DESC
However, when I attempted to add a CTE (see below) that displays each hire's final "post hire check in score", the query only returns 236 records. Ideally, I'd like the query to either return a score or null value for each of the initial 478 hire records.
WITH final_post_hire_score (candidate_id, final_score) AS
(SELECT c.candidate_id
,p.score
FROM post_hire_followup_reviews p
LEFT JOIN candidate_jobs c ON c.id = p.candidate_job_id
WHERE p.check_in_number = 3)
SELECT c.candidate_id AS candidate_id
,o.name
,j.name AS job_title
,c.applied_from
,job_id AS job_id
,cjs.score AS smart_rank_score
,cjs.is_completed AS smartrank_completion_status
,c.hired_at
,final_score
FROM final_post_hire_score f
LEFT JOIN candidate_jobs c ON c.candidate_id = f.candidate_id
LEFT JOIN organizations o ON o.id = c.organization_id
LEFT JOIN candidate_job_surveys cjs ON cjs.candidate_job_id = c.id
LEFT JOIN jobs j ON j.id = c.job_id
WHERE o.name LIKE ANY ('{"%Tutor Doctor%"}')
AND c.hired_at :: date BETWEEN '2015-01-01' AND '2016-02-22'
ORDER BY 8 DESC
Missing records are due to the filter's, Move the filter's to ON condition else your LEFT OUTER JOIN will be implicitly converted to INNER JOIN
When you are using LEFT OUTER JOIN right table filter's should be present in ON condition else the NULL values for non matching records will get filtered
WITH final_post_hire_score (candidate_id, final_score)
AS (SELECT c.candidate_id,
p.score
FROM post_hire_followup_reviews p
LEFT JOIN candidate_jobs c
ON c.id = p.candidate_job_id
WHERE p.check_in_number = 3)
SELECT c.candidate_id AS candidate_id,
o.NAME,
j.NAME AS job_title,
c.applied_from,
job_id AS job_id,
cjs.score AS smart_rank_score,
cjs.is_completed AS smartrank_completion_status,
c.hired_at,
final_score
FROM final_post_hire_score f
LEFT JOIN candidate_jobs c
ON c.candidate_id = f.candidate_id
AND c.hired_at :: date BETWEEN '2015-01-01' AND '2016-02-22'
LEFT JOIN organizations o
ON o.id = c.organization_id
AND o.NAME LIKE ANY ( '{"%Tutor Doctor%"}' )
LEFT JOIN candidate_job_surveys cjs
ON cjs.candidate_job_id = c.id
LEFT JOIN jobs j
ON j.id = c.job_id
ORDER BY 8 DESC
I think there's an extra
WHERE p.check_in_number = 3
that isn't anywhere else.

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

How to add a subquery to return count of related rows

I have a working SELECT that returns info on each company (i.e. subscriber). Certain descriptive data is returned from inner joins on "code" tables. My problem is how to add a new column to the result which is a count of the number of users belonging to each subscriber. SubscriberID in the user table is a foreign key:
ALTER TABLE dbo.UserInfo WITH CHECK ADD CONSTRAINT FK_UserInfo_SubscriberID
FOREIGN KEY(SubscriberID) REFERENCES dbo.Subscriber (SubscriberID)
ON UPDATE CASCADE
ON DELETE CASCADE
I added the LEFT OUTER JOIN below and the GROUP BY but I cannot figure out why the parser complains that U.SubscriberID is an invalid column name).
SELECT SubscriberID
,SubscriberName
,DaysUntilExpired
,SubscriptionStatus SubscriptionStatusCode
, C.Description SubscriptionStatus
,U.NumberUsers
FROM dbo.Subscriber S
JOIN dbo.CodeValue C ON S.SubscriptionStatus = C.Value
JOIN dbo.CodeNamespace N ON N.ID = C.CodeNamespaceID AND N.Name = 'SubscriptionStatus'
JOIN dbo.CodeValue V ON S.NotificationStatus = V.Value
JOIN dbo.CodeNamespace X ON X.ID = V.CodeNamespaceID AND X.Name = 'NotificationStatus'
LEFT OUTER JOIN (SELECT Count(*) AS NumberUsers FROM dbo.UserInfo) AS U
ON S.SubscriberID = U.SubscriberID
GROUP BY
S.SubscriberID
,SubscriberName
,DaysUntilExpired
,S.SubscriptionStatus
,SubscriptionStatus
You are probably looking for the following. You need to inline the subquery
SELECT SubscriberID
,SubscriberName
,DaysUntilExpired
,SubscriptionStatus SubscriptionStatusCode
, C.Description SubscriptionStatus
,(SELECT Count(*) AS NumberUsers FROM dbo.UserInfo where SubscriberID = S.SubscriberID) AS NumberUsers
FROM dbo.Subscriber S
JOIN dbo.CodeValue C ON S.SubscriptionStatus = C.Value
JOIN dbo.CodeNamespace N ON N.ID = C.CodeNamespaceID AND N.Name = 'SubscriptionStatus'
JOIN dbo.CodeValue V ON S.NotificationStatus = V.Value
JOIN dbo.CodeNamespace X ON X.ID = V.CodeNamespaceID AND X.Name = 'NotificationStatus'
GROUP BY
S.SubscriberID
,SubscriberName
,DaysUntilExpired
,S.SubscriptionStatus
,SubscriptionStatus
Sql complains because your LEFT OUTER JOIN subquery does not include SubscriberId in column list (and in a group by clause since you have a COUNT aggregate), so it can't make the join on this column.
You should do:
LEFT OUTER JOIN (SELECT SubscriberID , Count(*) AS NumberUsers FROM dbo.UserInfo GROUP BY SubscriberID ) AS U
ON S.SubscriberID = U.SubscriberID