Left Outer Joins from Two Tables using MS Access - sql

I'm working on an existing Access database trying to implement some changes which will allow the estimation of cutting times for my employer (We're an industrial company who fabricate Flight Cases). This should function on only certain types of components; We've got a Stock table, which holds information on each Stock item including it's category. A Specification table which is used in order to build an estimate/quotation, and a table named [Spec Components] which holds the list of Stock Items which are attached to a Specification.
The tables can be joined as follows : Stock INNER JOIN [Spec Components] ON Stock.ID = [Spec Components].[Stock ID]
Specification INNER JOIN [Spec Components] ON Specification.SpecID = [Spec Components].[Spec ID]
My problem is that I only want to apply cutting times to an item in [Spec Components] if the item is listed as "Panels", "Extrusions", "Hybrids" etc (which is information that can be queried via Stock.Category) and different variables are used depending on the type of item we're quoting for, for example a Fabricated Lid Case's Panel may require 18 cuts but a different case may require 26. The case type is something which can be retrieved via Specification.CaseType, and determines which type of case we're quoting for.
Initially I tried to tackle this problem using the fast and dirty solution of nested conditional statements within the SQL Query however eventually got the error "Query too complex", as there is a limit on the number of nested ifs.
What I'm attempting now is to use a separate table which contains the list of the different cuts/setups etc
Category | CaseType | Setups | Cuts | PCID
--------------------------------------------
Panels | Lidmaker | 2 | 32 | 1
Panels | Fab Lid | 4 | 16 | 1
Extrusion | Lidmaker | 1 | 24 | 1
I then need to be able to access the contents of this table where applicable, but still be able to retrieve the values from my other tables for which the contents of the table are not applicable (Which, to me, identifies the need for a Left Outer Join on this table).
I can do this using design view in MS Access:
However when I run the query I get this message, but I don't really understand what it's telling me to do, or how on earth I should separate the queries, perhaps I'm being silly?
The query itself goes something like this:
SELECT [Spec Components].Qty, Specification.Height, Specification.Width, Specification.Depth, IIf(Cutting.Cuts>0 And Cutting.Setup>0,(Cutting.Cuts*Stock.CutTime)+(Cutting.Setup*Stock.SetupTime),0)
FROM ((Stock INNER JOIN [Spec Components] ON Stock.ID = [Spec Components].[Stock ID]) INNER JOIN Specification ON [Spec Components].[Spec ID] = Specification.SpecID) LEFT JOIN Cutting ON (Stock.Category = Cutting.Category) AND (Specification.[Case Type] = Cutting.CaseType)
ORDER BY [Spec Components].[Stock ID];

EDIT : possible duplicate of Ambiguous left joins in MS Access
About the error message, you can find more information following these links:
http://rogersaccessblog.blogspot.fr/2008/09/ambiguous-outer-joins.html
http://support.microsoft.com/kb/124937/en-us
Excerpt from 'Fixing Access Annoyances'
To solve the issue, "You must specify which method should be used by changing one of the joins or by separating the query into two queries." (quoted from the second link).
Below is a way of handling the issue using a subquery.
SELECT
Specified_Stock.Qty,
Specified_Stock.Height,
Specified_Stock.Width,
Specified_Stock.Depth,
IIf(
Cutting.Cuts > 0 And Cutting.Setup > 0,
(Cutting.Cuts * Specified_Stock.CutTime) + (Cutting.Setup * Specified_Stock.SetupTime),
0
)
FROM (
SELECT
[Spec Components].[Stock ID],
[Spec Components].Qty,
Specification.Height,
Specification.Width,
Specification.Depth,
Stock.CutTime,
Stock.SetupTime,
Stock.Category,
Specification.[Case Type]
FROM Stock
INNER JOIN [Spec Components] ON Stock.ID = [Spec Components].[Stock ID]
INNER JOIN Specification ON [Spec Components].[Spec ID] = Specification.SpecID
) as Specified_Stock
LEFT JOIN Cutting ON (Specified_Stock.Category = Cutting.Category) AND (Specified_Stock.[Case Type] = Cutting.CaseType)
ORDER BY Specified_Stock.[Stock ID];

Related

SQL - join three tables based on (different) latest dates in two of them

Using Oracle SQL Developer, I have three tables with some common data that I need to join.
Appreciate any help on this!
Please refer to https://i.stack.imgur.com/f37Jh.png for the input and desired output (table formatting doesn't work on all tables).
These tables are made up in order to anonymize them, and in reality contain other data with millions of entries, but you could think of them as representing:
Product = Main product categories in a grocery store.
Subproduct = Subcategory products to the above. Each time the table is updated, the main product category may loses or get some new suproducts assigned to it. E.g. you can see that from May to June the Pulled pork entered while the Fishsoup was thrown out.
Issues = Status of the products, for example an apple is bad if it has brown spots on it..
What I need to find is: for each P_NAME, find the latest updated set of subproducts (SP_ID and SP_NAME), and append that information with the latest updated issue status (STATUS_FLAG).
Please note that each main product category gets its set of subproducts updated at individual occasions i.e. 1234 and 5678 might be "latest updated" on different dates.
I have tried multiple queries but failed each time. I am using combos of SELECT, LEFT OUTER JOIN, JOIN, MAX and GROUP BY.
Latest attempt, which gives me the combo of the first two tables, but missing the third:
SELECT
PRODUCT.P_NAME,
SUBPRODUCT.SP_PRODUCT_ID, SUBPRODUCT.SP_NAME, SUBPRODUCT.SP_ID, SUPPRODUCT.SP_VALUE_DATE
FROM SUBPRODUCT
LEFT OUTER JOIN PRODUCT ON PRODUCT.P_ID = SUBPRODUCT.SP_PRODUCT_ID
JOIN(SELECT SP_PRODUCT_ID, MAX(SP_VALUE_DATE) AS latestdate FROM SUBPRODUCT GROUP BY SP_PRODUCT_ID) sub ON
sub.SP_PRODUCT_ID = SUBPRODUCT.SP_PRODUCT_ID AND sub.latestDate = SUBPRODUCT.SP_VALUE_DATE;
Trying to find a row with a max value is a common SQL pattern - you can do it with a join, like your example, but it's usually more clear to use a subquery or a window function.
Correlated subquery example
select
PRODUCT.P_NAME,
SUBPRODUCT.SP_PRODUCT_ID, SUBPRODUCT.SP_NAME, SUBPRODUCT.SP_ID, SUPPRODUCT.SP_VALUE_DATE,
ISSUES.STATUS_FLAG, ISSUES.STATUS_LAST_UPDATED
from PRODUCT
join SUBPRODUCT
on PRODUCT.P_ID = SUBPRODUCT.SP_PRODUCT_ID
and SUBPRODUCT.SP_VALUE_DATE = (select max(S2.SP_VALUE_DATE) as latestDate
from SUBPRODUCT S2
where S2.SP_PRODUCT_ID = SUBPRODUCT.SP_PRODUCT_ID)
join ISSUES
on ISSUES.ISSUE_ID = SUBPRODUCT.SP_ID
and ISSUES.STATUS_LAST_UPDATED = (select max(I2.STATUS_LAST_UPDATED) as latestDate
from ISSUES I2
where I2.ISSUE_ID = ISSUES.ISSUE_ID)
Window function / inline view example
select
PRODUCT.P_NAME,
S.SP_PRODUCT_ID, S.SP_NAME, S.SP_ID, S.SP_VALUE_DATE,
I.STATUS_FLAG, I.STATUS_LAST_UPDATED
from PRODUCT
join (select SUBPRODUCT.*,
max(SP_VALUE_DATE) over (partition by SP_PRODUCT_ID) as latestDate
from SUBPRODUCT) S
on PRODUCT.P_ID = S.SP_PRODUCT_ID
and S.SP_VALUE_DATE = S.latestDate
join (select ISSUES.*,
max(STATUS_LAST_UPDATED) over (partition by ISSUE_ID) as latestDate
from ISSUES) I
on I.ISSUE_ID = S.SP_ID
and I.STATUS_LAST_UPDATED = I.latestDate
This often performs a bit better, but window functions can be tricky to understand.

Conditional join that changes number of join conditions

I am trying to join data based on the following scenario.
Let's say there are two businesses. Business 1 has one field for customer data, business 2 has two fields. I need to join to multiple other tables using these customer fields.
I would like to create a join that joins on just field 1 for business 1, but field 1 AND field 2 for business 2. In other words, there is a more granular identifier available for business 2, but it is still valid to join on just field 1 for business 1 as well. It also needs to function like an inner join, in that we are only preserving the relevant data that match these conditions.
The code would look something like this for business 1:
FROM customer_data a
INNER JOIN marketing_data b
ON a.member_number = b.member_number
WHERE business_number = 1
And something like this for business 2:
FROM customer_data a
INNER JOIN marketing_data b
ON a.member_number = b.member_number
AND a.sub_member_number = b.sub_member_number
WHERE business_number = 2
I am hoping to extract both sets of data in one join statement. Also, just in case it helps, I am using the Snowflake platform to write my queries.
Following should work for both the cases.
FROM customer_data a
INNER JOIN marketing_data b ON a.member_number = b.member_number
WHERE (
a.sub_member_number = b.sub_member_number
AND business_number = 2
)
OR business_number = 1
You can put the conditions in the ON clause like this:
FROM customer_data cd INNER JOIN
marketing_data md
ON cd.member_number = md.member_number AND
( cd.business_number <> 2 OR
cd.sub_member_number = md.sub_member_number
)
Note: this generalizes beyond just businesses 1 and 2, with the special condition only applying to 2. The first condition can be = 1 if you want to be more specific.
Also note that this introduces meaningful table aliases rather than arbitrary letters. This makes queries much easier to understand.

SQL query for crystal reports produces duplicate results

I created 3 tables TstInvoice, TstProd, TstPersons and added some data:
INVOICE_NBR CLIENT_NR VK_CONTACT
A10304 003145 AT
A10305 000079 EA
A10306 004458 AT
A10307 003331 JDJ
PROD_NR INVOICE_NBR
P29366 A10304
P29367 A10304
P29368 A10305
P29369 A10306
P29370 A10306
P29371 A10307
PERS_NR INITIALEN STATUS PERSOON
0001 AT 7 Alice Thompson
0002 EA 1 Edgar Allen
0003 JDJ 1 John Doe Joe
0004 AT 1 Arthur Twins
The parameter that is passed to the crystal report is the INVOICE_NBR.
On my crystal report I put some fields from the databases and one sql expression:
(
SELECT "TstPersons"."PERSOON" FROM "TstPersons"
WHERE "TstPersons"."INITIALEN" = "TstInvoice"."VK_CONTACT" AND "TstPersons"."STATUS" = 1
)
The full query that is generated:
SELECT "TstInvoice"."INVOICE_NBR", "TstInvoice"."CLIENT_NR", "TstPersons"."STATUS", "TstPersons"."PERSOON", "TstProd"."PROD_NR", "TstProd"."INVOICE_NBR", (
SELECT "TstPersons"."PERSOON" FROM "TstPersons"
WHERE "TstPersons"."INITIALEN" = "TstInvoice"."VK_CONTACT" AND "TstPersons"."STATUS" = 1
)
FROM ("GCCTEST"."dbo"."TstInvoice" "TstInvoice" INNER JOIN "GCCTEST"."dbo"."TstProd" "TstProd" ON "TstInvoice"."INVOICE_NBR"="TstProd"."INVOICE_NBR") INNER JOIN "GCCTEST"."dbo"."TstPersons" "TstPersons" ON "TstInvoice"."VK_CONTACT"="TstPersons"."INITIALEN"
WHERE "TstInvoice"."INVOICE_NBR"='A10304'
The result is as shown in the screenshot:
As you can see the TstPersons.PERSOON field is populated with Alice Thompson and the sql expression field is correctly populated with Arthur Twins. However, I would like only to see the prod_nr once. With this query it produces the prod numbers twice because of the double entry for "AT" despite the fact that I ask for only status 1. I could just delete the old entry but I want to know if it's possible this way.
* edit * I added the status = 1 to the "record selection formula editor" and that seems to work. Not need the sql expression field at all. Not sure if this is the correct way to go though.
So now it looks like this:
SELECT "TstInvoice"."INVOICE_NBR", "TstInvoice"."CLIENT_NR", "TstPersons"."STATUS", "TstPersons"."PERSOON", "TstProd"."PROD_NR", "TstProd"."INVOICE_NBR"
FROM ("GCCTEST"."dbo"."TstInvoice" "TstInvoice" INNER JOIN "GCCTEST"."dbo"."TstProd" "TstProd" ON "TstInvoice"."INVOICE_NBR"="TstProd"."INVOICE_NBR") INNER JOIN "GCCTEST"."dbo"."TstPersons" "TstPersons" ON "TstInvoice"."VK_CONTACT"="TstPersons"."INITIALEN"
WHERE "TstInvoice"."INVOICE_NBR"='A10304' AND "TstPersons"."STATUS"=1
You have a very weak join in your query due to the duplicate values found in the INITIALEN column. Using the STATUS = 1 criteria is a work-around more than a solution because if you ever need to report on an invoice where the contact has a status other than 1, you will need to modify the report's design to allow your join to work because the STATUS value is not found on the invoice to allow a proper join to occur.
You are also running a risk of this work-around breaking down completely should you have another contact with both the same initials and status values as another.
The correct way to solve this problem would be to join TstInvoice to TstPersons through a field that has unique values. The PERS_NR column appears to be a good choice for this.
This is also going to require a redesign of the TstInvoice table to include the PERS_NR column as a Foreign Key.
A stronger join between invoices and persons would also remove the need for that sub-query in you selection statement. This would simplify your query down to the following:
SELECT "TstInvoice"."INVOICE_NBR", "TstInvoice"."CLIENT_NR", "TstPersons"."STATUS", "TstPersons"."PERSOON", "TstProd"."PROD_NR", "TstProd"."INVOICE_NBR"
FROM "GCCTEST"."dbo"."TstInvoice" "TstInvoice"
INNER JOIN "GCCTEST"."dbo"."TstProd" "TstProd"
ON "TstInvoice"."INVOICE_NBR"="TstProd"."INVOICE_NBR"
INNER JOIN "GCCTEST"."dbo"."TstPersons" "TstPersons"
ON "TstInvoice"."PERS_NR"="TstPersons"."PERS_NR"
WHERE "TstInvoice"."INVOICE_NBR"='A10304'

Sql - how to build query with 3 tables

I'm developing a page that allow to search a product by reference or by bar code or by alternative bar codes in certain order list.
I need to get the reference and qtt columns from select.
To do that we have 3 tables:
bi (list all products inside a specific order)
sc (give us standard qtt by product reference)
bc (gives us alternative bar codes, this table has also qtt column)
The problem is that it's possible that a product doesn't have alternative bar codes and in this case the bc table returns null and in this case I have to get the qtt in table sc but I don't know how to do that in same query.
My query is that:
select top 1 bi.ref, bc.qtt
from bi left join
bc
on bc.ref = bi.ref
where (bi.ref='00012' or bi.code='00012' or bc.code='00012') and
bi.bostamp = ('orderID-0001')
The column bi.bostamp is the reference with order id.
So, I need to try integrate sc table in query to get qtt just when bc is null.
Thank you
I think you can use Case When to get the results you want.
Select Top 1 bi.ref, case when isnull(bc.qtt) then sc.qtt else bc.qtt end as qtt
FROM bi left join bc on bc.ref = bi.ref
left join sc on sc.ref = bi.ref
Does that work for you? This also assumes sc has a ref column like bc and bi.

How to make a query to obtain only results that have N number within a range of values?

I'm trying to extract nutrient data in MS Access 2007 from the USDA food database, freely available at http://www.ars.usda.gov/Services/docs.htm?docid=24912
I need records that have ALL nutrients from NUT_DATA.Nutr_No . Those records have values between '501' and '511' . But I wish to exclude incomplete records that have missing values.
Currently, Baby food banana has all from nutrient 501 to 511, but Baby food Beverage has only 9 of the nutrients listed, and many others are like that.
As a last resort, I guess it would be acceptable to have all records, showing null for missing values, as long as each FOOD_DES.Long_Desc has exactly 11 records, one for each NUT_DATA.Nutr_No OR NUTR_DEF.NutrDesc (which correspond to each other).
SELECT
FOOD_DES.NDB_No, FOOD_DES.FdGrp_Cd, FOOD_DES.Long_Desc, NUT_DATA.Nutr_No, NUTR_DEF.NutrDesc, NUT_DATA.Nutr_Val, WEIGHT.Amount, WEIGHT.Msre_Desc, WEIGHT.Gm_Wgt, [WEIGHT]![Amount] & " " & [WEIGHT]![Msre_Desc] AS msre
FROM
NUTR_DEF inner JOIN ((FOOD_DES INNER JOIN NUT_DATA ON FOOD_DES.NDB_No=NUT_DATA.NDB_No) INNER JOIN WEIGHT ON FOOD_DES.NDB_No=WEIGHT.NDB_No) ON NUTR_DEF.Nutr_No=NUT_DATA.Nutr_No
WHERE
(NUT_DATA.Nutr_No between '501' and '511' ) and ((WEIGHT.Seq)="1") and NUT_DATA.Nutr_Val > '0' and
// this part is me out of ideas trying stuff, but didn't help
EXISTS (SELECT 1
FROM
NUTR_DEF inner JOIN ((FOOD_DES INNER JOIN NUT_DATA ON FOOD_DES.NDB_No=NUT_DATA.NDB_No) INNER JOIN WEIGHT ON FOOD_DES.NDB_No=WEIGHT.NDB_No) ON NUTR_DEF.Nutr_No=NUT_DATA.Nutr_No
WHERE count FOOD_DES.Long_Desc = "11" )
//end wild of experimentation
ORDER BY FOOD_DES.Long_Desc, NUTR_DEF.SR_Order;
This is a sample of the data. I just copied the most important columns. The red is not what I'm looking for because it doesn't have all 11 nutrients. I can paste on the google doc the whole table if someone thinks that would help.
https://docs.google.com/spreadsheets/d/1FghDD59wy2PYlpsqUlYVc3Ulwvy4MMLagpBUYtvLBfI/edit?usp=sharing
As your starting point, identify which food items have values > 0 for all 11 of those nutrients. Check whether this simpler GROUP BY query shows you the correct items:
SELECT ndat.NDB_No
FROM
NUT_DATA AS ndat
INNER JOIN WEIGHT AS wt
ON ndat.NDB_No = wt.NDB_No
WHERE
ndat.Nutr_Val>0
AND ndat.Nutr_No IN('501','502','503','504','505','506','507','508','509','510','511')
AND wt.Seq='1'
GROUP BY ndat.NDB_No
HAVING Count(ndat.Nutr_No)=11;
Note you could use Val(ndat.Nutr_No) Between 501 And 511 as the Nutr_No restriction, which would give you a more concise statement. However, evaluating Val() for every row of the table means that approach would forego the performance benefit of indexed retrieval ... so that version of the query should be noticeably slower.
Save that query and create a new query which joins it to the base tables for the additional data you need from other columns. Or use it as a subquery instead of a named query if you prefer.