INNER JOIN clause ignoring NULL values - sql-server

I am looking to query some data that pertains to medications a patient has been prescribed that are in a certain category. But I also want to show patients that do not have any medications. My query so far:
SELECT
pd.fname,
pd.lname,
pp.drug_name,
pp.drug_strength
FROM
patient_data pd
FULL OUTER JOIN patient_prescr pp on pp.pid = pd.pid
FULL OUTER JOIN formulary f on pp.med_id = f.id
INNER JOIN formulary_categories fc on f.category = fc.id AND fc.id in (34,36,37,38,5)
WHERE
pd.lname = 'Test'
When applying the INNER JOIN to formulary_categories, I can correctly specify the category in which the drug I want to specify, but when I do this, it WILL NOT include patients that do not have any medications.
With the INNER JOIN joining the formulary_categories table, my results look like this:
-----------------------------------------------------------------------
fname | lname | drug_name | drug_strength
-----------------------------------------------------------------------
Cathy Test Clonazepam 0.5mg
Larry Test Librium 25mg
Jennifer Test Vistrail 25mg
-----------------------------------------------------------------------
If I change the INNER JOIN to a FULL OUTER JOIN, it simply ignores the category constraint, and pulls all categories.
However, the query will not include patients that do not have any medications prescribed. Id like my results to look something like:
-----------------------------------------------------------------------
fname | lname | drug_name | drug_strength
-----------------------------------------------------------------------
Cathy Test Clonazepam 0.5mg
Larry Test Librium 25mg
Joe Test NULL NULL
Jennifer Test Vistrail 25mg
Steve Test NULL NULL
-----------------------------------------------------------------------

You are actually looking for LEFT JOIN:
SELECT
pd.fname,
pd.lname,
pp.drug_name,
pp.drug_strength
FROM
patient_data pd
FULL OUTER JOIN patient_prescr pp on pp.pid = pd.pid
FULL OUTER JOIN formulary f on pp.med_id = f.id
LEFT JOIN formulary_categories fc on f.category = fc.id
AND fc.id in (34,36,37,38,5)
WHERE
pd.lname = 'Test'
A LEFT JOIN will not filter data if a correlation is not found between the values in the two tables (or result sets) and will display a NULL value for the columns which displays data from the table where a correlation was not found (just like in your expected output sample).
You can also take a look at the best article (in my opinion) for understanding all types of JOINs, here.

You simply need LEFT OUTER JOIN :
SELECT
pd.fname,
pd.lname,
pp.drug_name,
pp.drug_strength
FROM
patient_data pd
FULL OUTER JOIN patient_prescr pp on pp.pid = pd.pid
FULL OUTER JOIN formulary f on pp.med_id = f.id
LEFT OUTER JOIN formulary_categories fc on f.category = fc.id AND fc.id in (34,36,37,38,5)
WHERE
pd.lname = 'Test'

Related

Find third largest quote ever created for each of the accounts in the EC1 area

Can anyone help I'm new to SQL and trying to figure out the below question see image for the table structure;
Question = Select account name, contact last name, case number, quote number, quote date and quote value for the f third-largest quote ever created for each of the accounts in the EC1 area
So far I got;
Select
a.accountname, cc.lastname, c.casenumber,
q.quotenumber, q.quotedate, q.quotevalue
from
TBL_Quote q
Left join
TBL_case c On q.caseid = c.caseid
Left join
tbl_contact cc On c.contactID = cc. contactID
Left join
tbl_account a On a.accountid = cc.accountid
Where
left(a.postcode, 3) like 'EC1'
and for the third:
SELECT TOP 1 value
FROM
(SELECT DISTINCT TOP 3 value
FROM tbl_quote
ORDER BY value DESC) a
ORDER BY value
I can't seem to combine the top 3 and the query is it best to overpartion by ?
I would suggest joins and a row-limiting clause:
select ac.accountName, co.lastName, ca.caseNumber, qu.quoteNumber
from tbl_account ac
inner join tbl_contact co on co.accountId = ac.accountId
inner join tbl_case ca on ca.contactId = co.contactId
inner join tbl_quote qu on qu.caseId = ca.quoteId
where ac.postcode like 'EC1%'
order by len(qu.value) desc
offset 2 rows fetch next 1 row only

Join 4 tables and sum quantity for 2 tables using id from one table

My tables:
Order is:
PurchaseOrderHead
PurchaseOrder
ReceivingNoteHead
ReceivingNote
I want the output like this
MaterialID, PO.Quantity, RN.Quantity so far
There can be multiple receiving notes for a given purchaseorderhead_id as every ReceivingNoteHead will have a PurchaseOrderHeadID.
My attempt:
select
PurchaseOrder.MaterialID,
sum(distinct PurchaseOrder.Quantity) as "Sum_Quantity",
sum(ReceivingNote.Quantity) as "ReceivingNote_Quantity",
PurchaseOrderHead.id
from
(((dbo.PurchaseOrder
inner join
dbo.PurchaseOrderHead on (PurchaseOrderHead.id = PurchaseOrder.PurchaseOrderHeadID))
left outer join
dbo.ReceivingNoteHead ReceivingNoteHead (ReceivingNoteHead.PurchaseOrderHeadID = PurchaseOrderHead.id))
left outer join
dbo.ReceivingNote on (ReceivingNote.ReceivingNoteHeadID = ReceivingNoteHead.id))
group by
PurchaseOrder.MaterialID,
PurchaseOrderHead.id
having
(PurchaseOrderHead.id = 1004)
But ReceivingNote Quantities are repeated when there's no ReceivingNote MaterialID that matches PurchaseOrder's MaterialID.
This also does not work when theres multiple same MaterialID in either PurchaseOrder or ReceivingNote
I would like to learn whether I need to break the ReceivingNote table into 2 tables because of PurchaseOrderHeadID? And I want to get rid of the sum distinct because it's not the way I want it to be.
Maybe by first aggregating the material purchases in a sub-query.
Then left join that to the materials on the receiving end.
Untested notepad scribble:
SELECT
poMat.MaterialID,
poMat.TotQuantity AS [PurchaseOrder_Quantity],
SUM(rn.Quantity) AS [ReceivingNote_Quantity],
poMat.PurchaseOrderHeadID
FROM
(
SELECT
po.PurchaseOrderHeadID,
po.MaterialID,
SUM(po.Quantity) AS TotQuantity
FROM dbo.PurchaseOrder po
-- Uncomment to filter on the PurchaseOrderHeadID
-- WHERE po.PurchaseOrderHeadID = 1004
GROUP BY
po.PurchaseOrderHeadID,
po.MaterialID
) poMat
LEFT JOIN dbo.ReceivingNoteHead rnH
ON rnH.PurchaseOrderHeadID = poMat.PurchaseOrderHeadID
LEFT JOIN dbo.ReceivingNote rn
ON rn.ReceivingNoteHeadID = rnH.id
AND rn.MaterialID = poMat.MaterialID
GROUP BY
poMat.PurchaseOrderHeadID,
poMat.MaterialID,
poMat.TotQuantity
ORDER BY
poMat.PurchaseOrderHeadID,
poMat.MaterialID;
This however, won't show received materials that don't have a matching purchased material.
You are getting duplicate because the table ReceivingNoteHead does not have the PurchaseOrder.ID in it. Add the column PurchaseOrderID in ReceivingNoteHead and you should be good to go
select
PurchaseOrder.MaterialID,
sum(PurchaseOrder.Quantity) as "Sum_Quantity",
sum(ReceivingNote.Quantity) as "ReceivingNote_Quantity",
PurchaseOrderHead.id
from
dbo.PurchaseOrder
inner join
dbo.PurchaseOrderHead on PurchaseOrderHead.id = PurchaseOrder.PurchaseOrderHeadID
left outer join
dbo.ReceivingNoteHead ReceivingNoteHead ReceivingNoteHead.PurchaseOrderHeadID = PurchaseOrderHead.id *and ReceivingNoteHead.PurchaseOrderID=PurchaseOrder.ID*
left outer join
dbo.ReceivingNote on ReceivingNote.ReceivingNoteHeadID = ReceivingNoteHead.id
group by
PurchaseOrder.MaterialID,
PurchaseOrderHead.id
having
PurchaseOrderHead.id = 1004

IS NULL being ignored

I am trying to run a query in T-SQL to pull back a data set based on a column being null.
This is a simplified version of the code:
SELECT
T1.Col1, T1.Col2,
T1.Col3, T1.Col4
FROM
table1 AS T1
INNER JOIN
table2 AS T2 ON T1.Col2 = T2.Col3
WHERE
T2.Col4 IS NULL
Problem is, the result includes rows where T2.Col4 are NULL and also not NULL, it's like the WHERE clause doesn't exist.
Any ideas would be greatly
UPDATE - full version of code:
SELECT
M.ref
,C.cname
,CL.clname
,C.ccity
,M.productLine
,M.code
,CL.date
,M.dept
,DPT.group
,TK2.tkname
,TK2.tkdept
FROM DB.dbo.manage AS M
OUTER JOIN DB.dbo.ClientManageRelationship AS CMR
ON CMR.RelatedEntityID = M.EntityID
OUTER JOIN DB.dbo.Client AS C
ON C.EntityID = CMR.EntityID
INNER JOIN DB.dbo.ManageCustomerRelationship AS MCR
ON MCR.EntityID = M.EntityID
INNER JOIN DB.dbo.Customer AS CL
ON CL.EntityID = MCR.RelatedID
INNER JOIN DB.dbo.timek AS TK
ON TK.tki = M.tkid
LEFT JOIN (SELECT Group = division, [Department] = newdesc, deptcode FROM DB.csrt.vw_rep_p_l_dept) AS DPT
ON tkdept = DPT.dept
LEFT JOIN (SELECT Name = TK2.tkfirst + ' ' + TK2.tklast, TK2.tki, TK2.dept, TK2.loc FROM DB.dbo.timek as TK2 WITH(NOLOCK)) AS TK2
ON TK2.tki = M.tkid
WHERE DPT.Department = 'Casualty'
AND UPPER (C.ClientName) LIKE '%LIMITED%'
AND CL.date > '31/12/2014'
AND CL.Date IS NULL
AND TK.tkloc = 'loc1' OR TK.tkloc = 'loc2'
ORDER BY M.ref
My first answer would be because you're using INNER JOIN. This only returns matches between the 2 tables. TRY FULL OUTER JOIN which will return all values regardless of matches and will include NULLS.
If you were looking to return all rows regardless of matches including NULLS from only one of the tables then use RIGHT or LEFT JOIN.
Say i had 2 tables ('Person' and 'Figure'). Not every person may have entered a figure on any one day. But an example may be i want to return all people regardless of whether they entered a figure or not on a certain day.
My initial approach to this would be a LEFT join because i want to return of all the people(left table) regardless of there being any matches in the figure table(right table)
FROM Person P
LEFT JOIN Figure F
ON P.ID = F.ID
This would produce a result such as
Name Figure
Sam 20
Ben 30
Matt NULL
Simon NULL
Whereas,
An inner join would produce only matching values not including nulls
Name Figure
Sam 20
Ben 30
Left join works the same way as right join but in the opposite direction. This is most likely the problem you were facing. But i hope this helped
I think the problem is in the last part of the where condition.
You should use brackets.
`WHERE DPT.Department = 'Casualty'
AND UPPER (C.ClientName) LIKE '%LIMITED%'
AND CL.date > '31/12/2014'
AND CL.Date IS NULL
AND (TK.tkloc = 'loc1' OR TK.tkloc = 'loc2')`
or
`WHERE DPT.Department = 'Casualty'
AND UPPER (C.ClientName) LIKE '%LIMITED%'
AND CL.date > '31/12/2014'
AND CL.Date IS NULL
AND TK.tkloc IN ('loc1', 'loc2')`

Extract Specific Data After a aggregation (Or any other solution for the desired result)

I want to select the Total "sales" of a specific "main_category" for the year 2016
(main categories that don't have sales in that year should appear as zero)
I have managed to select the "sales" of a specific "main category" with all the other "main_categories" (that doesn't have any sales) appearing as zero using below query:
SELECT
mc.name,
ISNULL(SUM(s.no_of_units * b.unit_price),0) AS tCatSales
FROM Sales s
INNER JOIN Invoice i ON i.invoice_ID = s.invoice_id
INNER JOIN Inventory inv ON inv.inventory_ID = s.inventory_ID
INNER JOIN Batch b ON b.batch_ID = inv.batch_ID
INNER JOIN Products p ON p.product_id = b.product_ID
INNER JOIN Category c ON c.category_ID = p.category_id
RIGHT JOIN Main_Category mc ON mc.cat_id = c.main_category
--WHERE YEAR(i.trans_date) = 2016
GROUP BY mc.name
--HAVING YEAR(i.trans_date)=2016
but when I try to further segregate it for year 2016 ONLY either by WHERE clause or HAVING clause, it stops showing "main_category" names that have zero sales in the year.
One thing that I can think of is to give the query invoices only from 2016
which I tried to did by doing something like,
Replacing the line:
INNER JOIN Invoice i ON i.invoice_ID = s.invoice_id
with:
INNER JOIN Invoice i ON i.invoice_ID IN (SELECT invoice_id FROM Invoice in2 WHERE Year(in2.trans_date)=2016)
which did display the categories with zero values but with increased the calculated Sales Amount (from 2069 to something 203151022.75).
I understand this addition is somewhat illogical and disrupts the whole Inner Joins but so far these are the closest thing I can think of or find on the web.
I REPEAT the desired result is: main categories that don't have sales in that year should appear as zero with the year given year/month/date
As Sean and Eli mentioned, RIGHT JOIN is not recommended, you may change it to LEFT JOIN, OR use subquery like this:
SELECT
mc.name,
tCatSales = ISNULL(
(
SELECT
SUM(s.no_of_units * b.unit_price) AS tCatSales
FROM Sales s
INNER JOIN Invoice i ON i.invoice_ID = s.invoice_id
INNER JOIN Inventory inv ON inv.inventory_ID = s.inventory_ID
INNER JOIN Batch b ON b.batch_ID = inv.batch_ID
INNER JOIN Products p ON p.product_id = b.product_ID
INNER JOIN Category c ON c.category_ID = p.category_id
WHERE mc.cat_id = c.main_category
AND YEAR(i.trans_date) = 2016
) , 0)
FROM Main_Category mc
try this:
WHERE ISNULL(YEAR(i.trans_date), 1) = 2016
if you put simple equals conditions on outer join it will eliminate nulls, which give zero-valued rows you desire.
Also note that something like:
WHERE YEAR(i.trans_date) = 2016
is not sargable, see here

SQL Server - Join across several tables

Below are the relevant tables info:
tgenie
Guid | genietypeguid | ...
and :
tgenieType
guid | genietype | ...
I made this query to select all possible entries:
select
tsearch.description,
tcompany.CompanyName,
tsearch.CompanyGUID,
tgenie.GenieNotes,
tGenieType.GenieType
from
tcompany, tsearch, tGenieType
left outer join
tGenie on tgenie.GenieTypeGUID = tGenieType.GUID
As the result I have this output:
description | CompanyName | CompanyGUID | GenieNotes | GenieType
VP Buiness AP Dev | Test | 5920B842-8216-45AA-A2AF-EA69740959FC | Test2 | Add'l Candidate Info
VP Buiness AP Dev | Test | 5920B842-8216-45AA-A2AF-EA69740959FC | Test3 | Add'l Candidate Info
VP Buiness AP Dev | Test | 5920B842-8216-45AA-A2AF-EA69740959FC | Test5 | Add'l Candidate Info
.
.
.
However, it does not work as supposed.
GenieType is set as "Add'l Candidate Info" for all the entries. I had ran the query for a long time and it seem to be the same for all the rows ... which does not seem right.
Any body has an idea why my join does not work?
Thanks in advance!
Update 1
I ended up with this query
It took 9 mins and has 1478028 rows...
select tsearch.Description,
tcompany.CompanyName,
tcompany.GUID as CompanyGuid,
tcompanylocation.LocationName,
tsearchtype.SearchType,
tsearchresult.searchresult,
tpeople.GUID as PersonPlacedGuid,
tpeople.LastName As PersonPlacedLName,
tpeople.Firstname As PersonPlacedFName,
tpeople.address1,
tpeople.address2,
tpeople.city,
tpeople.zipcode,
tpeople.city2,
tpeople.zipcode2,
tpeople.emailaddress,
tpeople.website,
tpeople.homephone,
tpeople.OtherPhoneDescription1,
tpeople.otherphone1,
tpeople.otherphonedescription3,
tpeople.otherphone3,
tpeople.resumefile,
tpeople.resumeocr,
tpeople.timeentered,
tPeopleEducation.education,
tPeopleEducation.GradYear,
tsearch.SearchNotes,
( select tpeople.Firstname from tpeople where tpeople.guid=tSearch.RepresentativeGUID) as repfirstname,
( select tpeople.Lastname from tpeople where tpeople.guid=tSearch.RepresentativeGUID) as replastname,
tsearch.RepresentativeGUID as RepGuid,
tposition.Position as backgroundposition,
tdepartment.Department as backgrounddepartment,
( select tpeople.Lastname from tpeople where tpeople.guid=tSearch.ReferredByGUID) as referredbylastname,
( select tpeople.FirstName from tpeople where tpeople.guid=tSearch.ReferredByGUID) as referredbylastname,
tsearch.ReferredByGUID as PersonwhorefferedGuid,
( select tcompany.CompanyName from tCompany where tCompany.guid=tSearch.PlacedFromGUID) as placedfrom,
tinstantstatustype.InstantStatus,
tWorkbench.WorkbenchName,
( select tpeople.Lastname from tpeople where tpeople.guid=tInstantStatus.PeopleGUID) as Candlastname,
( select tpeople.FirstName from tpeople where tpeople.guid=tInstantStatus.PeopleGUID) as candFirstname,
tInstantStatus.ForClientNotes,
tinstantstatus.InstantStatusNotes as InstanttatusNotesSummary,
tGenie.GenieNotes,
tGenieType.GenieType,
tgenielabel.GenieLabel
from tcompany
inner join tsearch on tCompany.GUID = tSearch.CompanyGUID
left join tcompanylocation on tcompanylocation.guid= tcompany.LocationGUID
left join tSearchType on tsearchtype.GUID = tSearch.SearchTypeGUID
left join tSearchResult on tSearchResult.GUID = tsearch.SearchResultGUID
left join tPeople on tPeople.GUID = tsearch.PlacedGUID
left join tPosition on tPosition.GUID = tsearch.PositionGUID
left join tDepartment on tdepartment.GUID = tsearch.DepartmentGUID
left join tInstantStatus on tInstantStatus.SearchGUID = tSearch.guid
left join tInstantStatusType on tInstantStatusType.GUID = tInstantStatus.InstantStatusGUID
left join tWorkbench on tWorkbench.SearchGUID=tsearch.GUID
left join tSearchCluendex on tSearchCluendex.CPSGUID=tsearch.GUID
left join tpeopleEducation on tpeople.GUID = tpeopleEducation.PeopleGUID
left join tBusDev on tsearch.GUID = tbusdev.SearchGUID
left join tGenie on tbusdev.GUID = tgenie.BusDevGUID
left join tGenieLink on tGenie.GUID = tgenielink.GenieGUID
left join tGenieLabel on tgenielink.GenieLabelGUID = tGenieLabel.GUID
left join tGenieType on tgenie.GenieTypeGUID=tGenieType.GUID
Weird is that in the system 57 GenieType are defined and only 4 were found. Also with GenieLabel, in the system 16 are defined and only 6 were found.
I am beginner in SQL/TSQL and this query seems quite big .... does it look OK? Or it is hard to tell without more info about DB structure ...
HMMM.... there are different answers suggested, but this above seem to work.
Why should I use:
inner join
inner join
left outer
vs
inner join
left join
left join
vs
join
join
left join
????
Update 2:
Thanks #Daniel for your help. I attached the diagram as you suggested.
What I want to get can be seen in my query from Update 1
Hope some good soul can help.
Your query is performing a CROSS JOIN between tcompany, tsearch and tGenieType, and then tries to join on a corresponding record in the tGenie table. This means your query will return a record for every combination of tcompany, tsearch and tGenieType possible, regardless of any relations between them, and will then try to link the results to tGenie records using your condition, potentially multiplying the results once more.
When you say that you want "all possible entries", what exactly do you want to achieve?
Typically, as well as from your queried columns, you'd want to perform a INNER JOIN or a LEFT JOIN rather than a CROSS JOIN. For example :
SELECT
tSearch.Description,
tCompany.CompanyName,
tSearch.CompanyGUID,
tGenie.GenieNotes,
tGenieType.GenieType
FROM
tCompany
INNER JOIN tSearch ON tCompany.GUID = tSearch.CompanyGUID
LEFT JOIN tGenie ON tSearch.GUID = tGenie.SearchGUID
LEFT JOIN tGenieType ON tGenieType.GUID = tGenie.GenieTypeGUID
This would pull every tSearch from every tCompany, matching them according to their foreign keys, and then try to match with the corresponding tGenie records based on an hypothetical GUID column before doing the same with the tGenieType.
I suggest trying maybe this.
SELECT tsearch.description
,tcompany.CompanyName
,tsearch.CompanyGUID
,tgenie.GenieNotes
,tGenieType.GenieType
FROM tcompany
JOIN tsearch
ON tcompany.SomeID = tsearch.SomeID
JOIN tGenieType
ON tcompany.SomexID = tGenieType.SomexID
LEFT JOIN tGenie
ON tgenie.GenieTypeGUID = tGenieType.GUID
You accidentaly stumbled upon cross join. Also, connecting your tables will have to go over keys, so make sure you identify them correctly. A good visual reference on what's going on with joins is on codinghorror blog.
You are not joining the other tables in FROM (and this syntax is not longer supported in new versions of Sql Server)
Should be like this:
select tsearch.description
,tcompany.CompanyName
,tsearch.CompanyGUID
,tgenie.GenieNotes
,tGenieType.GenieType
from tcompany
inner join tsearch on tcompany.XXX = tsearch.XXX
inner join tGenieType on tcompany.YYY = tsearch.YYY
left outer join tGenie on tgenie.GenieTypeGUID=tGenieType.GUID

Resources