Query to bring back Records from left table if not in right table - sql

Imagine having a Stores table:
ID
StoreNumber
BranchName
Imagine having another "SalesData" table:
ID
Month
Year
StoreNumber
AmountAquired
Stores table is always populated. The SalesData is NOT. It is only populated as and when.
I am struggling to come up with the following query where it will bring me back results where for every store if there is no record for it for the given month and year AND where AmountAquired is null.
At the same time another added complexity (perhaps a separate query) is where to bring back all stores where there is no data in the sales data (with the join on StoreNumber) but if there is data then to only bring back the row for that store that has data and disregard the row which has no data for that store.
I have tried doing a left outer join and right outer join but no go.
SELECT s.* FROM Stores s
LEFT OUTER JOIN SalesData sd ON
sd.StoreNumber = s.StoreNumber
WHERE sd.Year = 2014 AND sd.Month = 11 AND sd.AmountAquired IS NULL.
this brings back only the records where the year and month is 2014 and 11 (not surprising since that is what I put down in the where clause!)
I want it where it brings me back records where there is no data in the salesdata OR where the month and year match the parameters given to it (and not for any other month or any other year unless NULL is in the column values)
It should ALWAYS bring back data (pretty much from the stores table to show all the stores...and any data not found for it in the sales data table (or even if data is there for the given month and year along with for stores which have no data for them)
Hope this makes sense.

You are correct in using a LEFT OUTER JOIN for this type of query. The mistake you made is that when performing a LEFT OUTER JOIN, you need to move filters that are related to the right table into the join on section.
To get only store that are missing sales for Nov 2014:
SELECT s.*
FROM Stores s
LEFT OUTER JOIN SalesData sd ON
sd.StoreNumber = s.StoreNumber
AND sd.Year = 2014 AND sd.Month = 11
WHERE sd.AmountAquired IS NULL --no sales are found
And to get all sales for Nov 2014, including stores that had no sales, you can do this:
SELECT s.*
FROM Stores s
LEFT OUTER JOIN SalesData sd ON
sd.StoreNumber = s.StoreNumber
AND sd.Year = 2014 AND sd.Month = 11

Can you not just switch to an OR for the 2nd case?
(sd.Year = 2014 AND sd.Month = 11) OR sd.AmountAquired IS NULL
In the 1st example you ask for "no record for it for the given month and year AND where AmountAquired is null" - but if there's no record, then AmountAquired will be NULL by necessity.
That query is simply:
SELECT
s.*
FROM
Stores s
LEFT OUTER JOIN SalesData sd ON sd.StoreNumber = s.StoreNumber
WHERE
sd.StoreNumber IS NULL

Related

SQL Query Creates Duplicated Results

My task is to produce a report that shows the on time delivery of products to consumers. In essence I have achieved this. However, as you will see only some of the data is accurate.
Here is our test case: we have a sales order number '12312.' This sales order has had 5 partial shipments made (200 pieces each). The result is shown below from our DUE_DTS table.
Due Dates table data
The following code gives me the information I need (excluding due date information) to show the packing details of the 5 shipments:
DECLARE #t AS TABLE (
CUSTNAME char(35),
SONO char(10),
INVDATE date,
PACKLISTNO char(10),
PART_NO char(25),
SOBALANCE numeric(9,2)
)
INSERT INTO #t
SELECT DISTINCT c.CUSTNAME, s.SONO, p.INVDATE, p.PACKLISTNO, i.PART_NO, q.SOBALANCE
FROM [manex].[dbo].[SODETAIL]
INNER JOIN [manex].[dbo].[SOMAIN] s ON s.SONO = SODETAIL.SONO
INNER JOIN [manex].[dbo].[CUSTOMER] c ON c.CUSTNO = s.CUSTNO
INNER JOIN [manex].[dbo].[INVENTOR] i ON i.UNIQ_KEY = SODETAIL.UNIQ_KEY
INNER JOIN [manex].[dbo].[DUE_DTS] d ON d.SONO = s.SONO
INNER JOIN [manex].[dbo].[PLMAIN] p ON p.SONO = s.SONO
INNER JOIN [manex].[dbo].[PLDETAIL] q ON q.PACKLISTNO = p.PACKLISTNO
WHERE s.SONO LIKE '%12312'
SELECT * FROM #t
Here is a screenshot of the results from running this query:
Query Result
Now is when it should be time to join my due dates table (adding in the appropriate column(s) to my table definition and select statement) and make DATEDIFF comparisons to determine if shipments were on time or late. However, once I reference the due dates table, each of the 5 shipments is compared to all 5 dates in the due dates table, resulting in 25 rows. The only linking column DUE_DTS has is the SONO column. I've tried using DISTINCT and variations of the group by clause without success.
I've put enough together myself to figure joining the DUE_DTS table on SONO must be causing this to happen, as there are 5 instances of that value in the table (making it not unique) and a join should be based on a unique column. Is there a workaround for something like this?
You will need to use additional fields to join the records and reduce the results. You may need to link SONO to SODETAIL to DUE_DTS because the dates are tied to the items, not to the SONO.

How to return multiple fields from subquery

I work in healthcare where we check a patient's insurance to see if their insurance is still active and is eligible for our services. Unfortunately, the database is not very good at associating the eligibility with an appointment or future visit (we have no control over this). As a result, we do not have visibility into what ultimately happens as a result of that eligibility check (did we use that insurance? did the patient not use insurance at all? etc).
What we'd like to do is try to associate the eligibility to a patient's visit that is within a day of that eligibility check (so same day or day before). I'd like to retrieve that visit number and the primary payer for that visit (I'd like the visit number so I can verify that I'm pulling in the correct visit as well as the correct primary payer).
How do I get both pieces of information in the results of my main query? Alternative: how can I spot check the results to verify it is working as desired if I'm only returning the payer name from an inline subquery?
Eligibility Query
select distinct
elig.patient_id,
pm.payer_name,
elig.status,
elig.reject_reason
from
eligibility elig
inner join payer_mstr pm on elig.payer_id=pm.payer_id
where
elig.create_timestamp>='2019-01-01' and elig.create_timestamp<'2019-02-01'
Associated Encounter and Payer Data and Tables
select distinct
pe.enc_nbr,
pm.payer_name
from
patient_encounter pe --visit data
--encounter_payer holds payer data for a specific visit, and 1 means primary payer
left join encounter_payer epay on pe.enc_id=epay.enc_id and cob=1
left join payer_mstr pm on epay.payer_id=pm.payer_id
I'm on Microsoft SQL Server 2008 R2
You could try using a common table expression. Works like a subquery and you can add all the fields you would like to have from it.
Common Table Expressions
WITH cte AS (
select
pe.enc_nbr,
pm.payer_name,
pm.payer_id,
ROW_NUMBER() OVER(PARTITION BY pm.payer_id ORDER BY pm.payer_id ASC) AS row_num --If you have a date created on each row I would change the pm.payer_id field that and change it to ORDER BY <field> DESC
from
patient_encounter pe --visit data
--encounter_payer holds payer data for a specific visit, and 1 means primary payer
left join encounter_payer epay on pe.enc_id=epay.enc_id and cob=1
left join payer_mstr pm on epay.payer_id=pm.payer_id
--Here you could add a WHERE clause to narrow your search date down to get previous or current date if you have a date field just use WHERE date >= DATEADD(d,-1,GETDATE())
)
select distinct
elig.patient_id,
pm.payer_name,
elig.status,
elig.reject_reason,
cte.enc_nbr
from
eligibility elig
inner join payer_mstr pm on elig.payer_id=pm.payer_id
left join cte ON cte.pm.payer_id = pm.payer_id --Now you can pull in any fields you need and can see if they have a record or do not
where
elig.create_timestamp>='2019-01-01' and elig.create_timestamp<'2019-02-01'
Hard to test your very specific question, but try this:
SELECT DISTINCT
elig.patient_id,
pm.payer_name,
elig.status,
elig.reject_reason
pe.enc_nbr
FROM
patient_encounter pe
LEFT JOIN encounter_payer epay on pe.enc_id=epay.enc_id and cob=1
LEFT JOIN payer_mstr pm on epay.payer_id=pm.payer_id
LEFT JOIN eligibility elig on elig.payer_id=pm.payer_id
WHERE
elig.create_timestamp>='2019-01-01' and elig.create_timestamp<'2019-02-01'

join to all rows of a table even without values sql server

I have a date table that I use to determine week of the year (our year is not calendar year based right now.). I have a union query that creates a table that I am going to use in a table valued function. I can create the table in the union query but it only produces the 40 weeks that have actual data. I need it to show the weeks regardless of whether data exists or not.
I have tried to left outer join on the week table but I still get only records where the week has data associated with it.
left outer join [dbo].[LOOKUP_WEEK_NUMBER] w on
(p.[Program_Year_ID] = w.[Program_Year_Nbr]) and
(p.[Installation_Complete_Date] between w.[Week_Start] and w.[Week_End])
LOOKUP_WEEK_NUMBER table has all the weeks of the year and a start and end date for each week. The p table contains the project data. What I am doing is bucketing the projects into weeks for reporting purposes.
Can anyone point me in the right direction? Thanks in advance.
change to a left join from your lookup table instead of to your lookup table, or use a right join to your lookup table.
from [dbo].[LOOKUP_WEEK_NUMBER] w
left join ... p
on (p.[Program_Year_ID] = w.[Program_Year_Nbr])
and (p.[Installation_Complete_Date] between w.[Week_Start] and w.[Week_End])

Compare 2 tables and add missing records to the first, taking into account year/months

I have 2 tables, one with codes and budgets called FACT_QUANTITY_TMP and the other is a tree with all possible codes called C_DS_BD_AP_A.
All codes that exist are in this C_DS_BD_AP_A table, yet not all are in FACT_QUANTITY_TMP. Only those with budget get added by the ERP.
We need all codes to be in this FACT_QUANTITY_TMP table, just with budget to be 0 in that case.
I was trying first to get the missing codes by the following query:
SELECT T2.D_ACTIECODE From
(SELECT distinct
A.FULL_DATE as FULL_DATE, A.DIM03 as DIM03
FROM FACT_QUANTITY_TMP A) T1
RIGHT JOIN
(select distinct B.D_ACTIECODE AS D_ACTIECODE from C_DS_BD_AP_A B) T2
ON
T1.DIM03 = T2.D_ACTIECODE
where T1.DIM03 is null
order by T1.full_date
I get a list of my missing records yet it doesn't take into accounts the FULL_DATE (year and month) of the destination table.
In short, FACT_QUANTITY_TMP needs to have all records added that it's missing grouped by months and year.
Kind of looking for the best approach here, this query would be used in a automatically run stored proc every month when the ERP data gets pulled.
You can generate the missing records by doing a cross join to generate all combinations and then removing those that are already there. For example:
select fd.fulldate, c.D_ACTIECODE
from (select distinct fulldate from fact_quantity_tmp) fd cross join
(select D_ACTIECODE from C_DS_BD_AP_A) c left join
fact_quantity_tmp fqt
on fqt.fulldate = fd.fulldate and fqt.dim03 = c.D_ACTIECODE
where fqt.fulldate is null;
You can put an insert before this to insert these rows into the fact table.

For Loop in SQL Server 2005 Express?

I have a program using SQL Server 2005 Express and I need some help looping through 2 tables to calculate inventory.
Table 1: stores all products with the inventory total upon setup
Table 2: stores the transactions against all products from table 1
How can I loop through all items in table 2 and subtract that amount from table 1 counts?
If I have my query like this then I get data for each product
SELECT
ii.ItemNum, ii.ItemName, ii.OzOnHand
FROM
dbo.InventoryItems ii
INNER JOIN
dbo.InventoryLog il ON ii.ItemNum = il.InvItemNum
WHERE
ii.active = 1
I need each occurrence from table 2 to be subtracted from table 1's total amount
This is an example of a join to an aggregated table (I think that is the best way to understand it):
SELECT ii.ItemNum, ii.ItemName, ii.OzOnHand, ii.OzOnHand - coalesce(il.cnt, 0)
FROM dbo.InventoryItems ii LEFT JOIN
(select il.InvItemNum, sum(OzRemoved) as cnt
from dbo.InventoryLog il
group by il.InvItemNum
) il
ON ii.ItemNum = il.InvItemNum
WHERE ii.active = 1;
The subquery groups everything in the log and counts the number of entries. If each entry could affect more than one item, then you would use something like sum(cnt) as cnt instead of count(*).
Then, the query uses a left outer join. This type of join ensures that all inventory items remain, even those with nothing in the log. Finally, the count is subtracted from what is available in set up. The coalesce() is to handle the situation where there are no matches in the log table. To avoid getting NULL, the NULL is turned into a 0.