SQL select for many to many relationship using binding table - sql

I am struggling with writing a select for diagram on the picture.
What I want to do is write a select, which will show me details of a car repair. As you can see in table Repairs there are only 2 attributes but I am not sure if it's necessary to add more, especialy those from employees_list and parts_list, since I want to show repair data for every vehicle by it's plate_number. By repair data I mean repair id, vehicle plate_number, all employees working on the repair and all parts used on the repair. If my diagram is wrong, please help me fix it and I have no idea how to write select for this because of the many to many relation and the use of binding tables.

This is not so hard as it may seem.
First, obviously, we have to select cars:
select vehicles.* from vehicles
then, let's join repairs:
select
vehicles.*
from vehicles
inner join repairs on vehicles.id = repairs.vehicle.id
We don't need data from repairs in resule set, so we just join it but not mention in 'select' part.
Then we have to join parts needed for repair, and info about parts itself:
select
vehicles.*
from vehicles
inner join repairs on vehicles.id = repairs.vehicle.id
inner join parts_list on parts_list.repair_id = repairs.id
inner join parts on parts_list.part_id = parts.id
For that query we get amout of rows equivalent of amount of parts needed for repair. But it would be more easy to handle such data in code if we aggregate all of them into json column. So in result set we willsee something like:
vehicle_id, vehicle_part, parts_needed_as_json
Lets aggregate this:
select
vehicles.*, json_agg(parts.*) as parts_needed
from vehicles
inner join repairs on vehicles.id = repairs.vehicle_id
inner join parts_list on parts_list.repair_id = repairs.id
inner join parts on parts_list.part_id = parts.id
group by vehicles.id, repairs.id
Now you can add same logic for employees:
select
vehicles.*,
json_agg(parts.*) as parts_needed,
json_agg(employes.*) as employees_needed
from vehicles
inner join repairs on vehicles.id = repairs.vehicle.id
inner join parts_list on parts_list.repair_id = repairs.id
inner join parts on parts_list.part_id = parts.id
inner join employees_list on employes_list.repair_id = repairs.id
inner join employees on employees_list.employee_id = employees.id
group by vehicles.id, repairs.id
BTW, I suggest you to rename your tables to lowercase and singulars.
Like: 'repair', 'employee' and 'vehicle';
Also, name your binding tables like: 'repair_part' and 'repair_employee'.
Some people even suggest to arrange related tables in that names by alphabet, like: 'employee_repair' and 'part_repair', but I think it's not required;
Maybe this is a question of taste but in most cases this leads to more readable queries.
I.e, the query above will looks like:
select
vehicle.*,
json_agg(part.*) as parts_needed,
json_agg(employee.*) as employees_needed
from vehicle
inner join repair on vehicle.id = repair.vehicle_id
inner join parts_repair on parts_repair.repair_id = repair.id
inner join part on parts_repair.part_id = part.id
inner join employees_repair on employees_repair.repair_id = repair.id
inner join employee on employees_repair.employee_id = employee.id
group by vehicle.id, repair.id
Note how elegant 'on' conditions looks now: parts_repair.part_id = part.id, parts_repair.part_id = part.id
Sorry for bad english

Related

Inner Join and Left Join on 5 tables in Access using SQL

I am attempting to access data from the following tables:
OrgPlanYear
ProjOrgPlnYrJunction
DC
DCMaxEEContribLevel
DCNonDiscretionaryContribLevel
Basically, I need to inner join OrgPlanYear + DC and ProjOrgPlnYrJunction then I need to Left Join the remaining tables (tables 4 and 5) due to the fact the tables 1-3 have all the rows I need and only some have data in tables 4-5. I need several variables from each table. I also need the WHERE function to be across all fields (meaning I want all this data for a select group where projectID=919).
Please help!
I have tried many things with errors including attempting to use the Design Query side (i.e. JOIN function issues, badly formatted FROM function, etc.)! Here is an example of one excluding all variables I need:
SELECT
ProjOrgPlnYrJunction.fkeyProjectID, OrgPlanYear.OrgName, DC.PlanCode, DCNonDiscretionaryContribLevel.Age,DCNonDiscretionaryContribLevel.Service
FROM
(((OrgPlanYear INNER JOIN DC ON OrgPlanYear.OrgPlanYearID = DC.fkeyOrgPlanYearID) INNER JOIN ProjOrgPlnYrJunction ON OrgPlanYear.OrgPlanYearID = ProjOrgPlnYrJunction.fkeyOrgPlanYearID)
LEFT JOIN
(SELECT DCNonDiscretionaryContribLevel.Age AS Age, DCNonDiscretionaryContribLevel.Service AS Service FROM DCNonDiscretionaryContribLevel WHERE ProjOrgPlnYrJunction.fkeyProjectID)=919)
LEFT JOIN (
SELECT DCMaxEEContribLevel.EEContribRoth FROM EEContribRoth WHERE ProjOrgPlnYrJunction.fkeyProjectID)=919)
ORDER BY OrgPlanYear.OrgName;
Main issues with your query:
Missing ON clauses for each LEFT JOIN.
Referencing other table columns in SELECT and WHERE of a different subquery (e.g., FROM DCNonDiscretionaryContribLevel WHERE ProjOrgPlnYrJunction.fkeyProjectID).
Unmatched parentheses around subqueries and joins per Access SQL requirements.
See below adjusted SQL that now uses short table aliases. Be sure to adjust SELECT and ON clauses with appropriate columns.
SELECT p.fkeyProjectID, o.OrgName, DC.PlanCode, dcn.Age, dcn.Service, e.EEContribRoth
FROM (((OrgPlanYear o
INNER JOIN DC
ON o.OrgPlanYearID = DC.fkeyOrgPlanYearID)
INNER JOIN ProjOrgPlnYrJunction p
ON o.OrgPlanYearID = p.fkeyOrgPlanYearID)
LEFT JOIN
(SELECT Age AS Age, Service AS Service
FROM DCNonDiscretionaryContribLevel
WHERE fkeyProjectID = 919) AS dcn
ON dcn.fkeyProjectID = p.fkeyOrgPlanYearID)
LEFT JOIN
(SELECT EEContribRoth
FROM EEContribRoth
WHERE fkeyProjectID = 919) AS e
ON e.fkeyProjectID = p.fkeyProjectID
ORDER BY o.OrgName;

PostgreSQL - Why 1 search paramater works, but not the other? (multiple joined tables)

So I have a query that I run that basically looks like the one below (simplified it though for easier viewing). When I search on user_orders.orderID it works flawlessly, but if I try to search by user_orders.reference instead it just timeouts.
Now I assume that this has something to do with some of the tables that I have joined not having the reference number in them, only the orderID is present in all of them. However it would greatly simplify my work if I could search on the reference number directly, instead of the orderID.
Any advice on how to solve it?
EDIT:
Solved now, thanks everyone! It was indeed the FULL OUTER JOIN that was causing the problems, didn't fully understand what it did compared to a regular JOIN.
SELECT
user_orders.reference,
user_orders.orderid,
transfers.username,
notifications.datestamp,
orders.refund,
users.acac,
refunds.state,
decisionlog.data
FROM user_orders
FULL OUTER JOIN decisionlog ON user_orders.orderid = decisionlog.orderid
FULL OUTER JOIN refunds ON user_orders.orderid = refunds.orderid
FULL OUTER JOIN notifications ON user_orders.orderid = notifications.orderid
JOIN transfers ON user_orders.orderid = transfers.orderid
JOIN orders ON transfers.orderid = orders.orderid
JOIN users ON transfers.username = users.username
WHERE user_orders.orderid = xxx;
As suggested in the comments, if you do not have an orderid in any of decisionlog, refunds, notifications tables, this query will return a null for notifications.datestamps, refunds.state, decisionlog.data , but it will not give you an error.
Plus, take into account that using JOIN (INNER JOIN) will return you only orderid's that are on the transfer and order table and whose users are found at users table
SELECT
user_orders.reference,
user_orders.orderid,
transfers.username,
notifications.datestamp,
orders.refund,
users.acac,
refunds.state,
decisionlog.data
FROM user_orders
LEFT JOIN decisionlog ON user_orders.orderid = decisionlog.orderid
LEFT JOIN refunds ON user_orders.orderid = refunds.orderid
LFET JOIN notifications ON user_orders.orderid = notifications.orderid
INNER JOIN transfers ON user_orders.orderid = transfers.orderid
INNER JOIN orders ON transfers.orderid = orders.orderid
INNER JOIN users ON transfers.username = users.username
WHERE user_orders.reference = xxx;

Is it true that all joins following a left join in a SQL query must also be left joins? Why or why not?

I remember this rule of thumb from back in college that if you put a left join in a SQL query, then all subsequent joins in that query must also be left joins instead of inner joins, or else you'll get unexpected results. But I don't remember what those results are, so I'm wondering if maybe I'm misremembering something. Anyone able to back me up on this or refute it? Thanks! :)
For instance:
select * from customer
left join ledger on customer.id= ledger.customerid
inner join order on ledger.orderid = order.id -- this inner join might be bad mojo
Not that they have to be. They should be (or perhaps a full join at the end). It is a safer way to write queries and express logic.
Your query is:
select *
from customer c left join
ledger l
on c.id = l.customerid inner join
order o
on l.orderid = o.id
The left join says "keep all customers, even if there is no matching record in ledger. The second says, "I have to have a matching ledger record". So, the inner join converts the first to an inner join.
Because you presumably want all customers, regardless of whether there is a match in the other two tables, you would use a left join:
select *
from customer c left join
ledger l
on c.id = l.customerid left join
order o
on l.orderid = o.id
You remember correctly some parts of it!
The thing is, when you chain join tables like this
select * from customer
left join ledger on customer.id= ledger.customerid
inner join order on ledger.orderid = order.id
The JOIN is executed sequentialy, so when customer left join ledger happens, you are making sure all joined keys from customer return (because it's a left join! and you placed customers to the left).
Next,
The results of the former JOIN are joined with order (using inner join), forcing the "the first join keys" to match (1 to 1) with the keys from order so you will end up only with records that were matched in order table as well
Bad mojo? it really depends on what you are trying to accomplish.
If you want to guarantee all records from customers return, you should keep "left joining" to it.
You can, however, make this a little more intuitive to understand (not necessarily a better way of writing SQL!) by writing:
SELECT * FROM
(
(SELECT * from customer) c
LEFT JOIN
(SELECT * from ledger) l
ON
c.id= l.customerid
) c_and_l
INNER JOIN (OR PERHAPS LEFT JOIN)
(SELECT * FROM order) as o
ON c_and_l.orderid (better use c_and_l.id as you want to refer to customerid from customers table) = o.id
So now you understand that c_and_l is created first, and then joined to order (you can imagine it as 2 tables are joining again)

Conditional statement to determine which table to Select from in Access 2013

I am trying to come up with a query to show the purchase and product information made by a member, and I was wondering if there is a way create a conditional statement to determine whether the product the member bought was from the Clothing table, Accessory table or if they bought a product from both tables. and the determining factor for which table is the ProductID in the Product table, if the user didn't but from the clothing table, the ProductID should be 0. The ProductID connects to both the Clothing and Accessory tables by their ProductTypeID, if you need any more information, let me know, thanks!
An image of the tables in Access is here
And here it is in SQL View
SELECT Member.MemberID, Member.FirstName, Member.LastName,
Purchase.PurchaseDate, LineItem.CalculatedPrice, Product.ProductType
FROM ClothingType
INNER JOIN ((AccessoryType INNER JOIN (((Member INNER JOIN Purchase
ON Member.MemberID = Purchase.MemberID)
INNER JOIN (Product INNER JOIN LineItem ON Product.ProductID = LineItem.ProductID)
ON Purchase.PurchaseID = LineItem.PurchaseID)
INNER JOIN Accessory ON Product.ProductID = Accessory.ProductTypeID)
ON AccessoryType.AccessoryTypeID = Accessory.AccessoryTypeID)
INNER JOIN Clothing ON Product.ProductID = Clothing.ProductTypeID)
ON ClothingType.ClothingTypeID = Clothing.ClothingTypeID;
you need to modify your query to use left joins. Unfortunately MS Access has its craziness about the parenthesis in the JOINS, so my query below may be erroneous. Please comment below if Access complains about errors in the JOIN clause, and I will do my best to fix it:
SELECT Member.MemberID, Member.FirstName, Member.LastName,
Purchase.PurchaseDate, LineItem.CalculatedPrice, Product.ProductType
FROM Member INNER JOIN (Purchase
INNER JOIN (Product INNER JOIN (LineItem
LEFT JOIN (Accessory LEFT JOIN (AccessoryType
LEFT JOIN (Clothing LEFT JOIN ClothingType ON
ClothingType.ClothingTypeID = Clothing.ClothingTypeID)
ON Product.ProductID = Clothing.ProductTypeID)
ON AccessoryType.AccessoryTypeID = Accessory.AccessoryTypeID)
ON Product.ProductID = Accessory.ProductTypeID)
ON Product.ProductID = LineItem.ProductID)
ON Purchase.PurchaseID = LineItem.PurchaseID)
ON Member.MemberID = Purchase.MemberID);
However, as I mentioned above, it may not work because of Access stupidity around its usage of JOINs. You may be better of to double-clicking on the last four join lines in the designer (to the right of Product) and make them all "Select all from the left table and only those that match from the right table" option (which is a LEFT JOIN in Access terms)
UPDATE: forgot to add: once you have your join working all you need to do is to use an expression for the Accessory/Clothing description like this:
Iif(IsNULL(AccessoryType.Description), ClothingType.Description, AccessoryType.Description) as Description

Joining multiple tables and getting multiple attributes from one of them

I'm trying to join multiple tables together for building a report. The report lists a course, revisions made to it, and who requested, made and approved the revisions.
Under requested, made an approved, the values are employee numbers. I'm trying to join my innerjoined table above, with the Employee table so I can list the names (not just employee numbers) of those that requested, made and approved revisions.
This is what I have which I know is totally wrong.
SELECT *
FROM Courses
INNER JOIN CourseRevisions ON CourseRevisions.PELID = Courses.PELID
INNER JOIN CourseGroups ON CourseGroups.CourseGroupID = Courses.CourseGroupID
INNER JOIN [dbo].[OPG_Employees] ON OPG_Employees.EmployeeID = CourseRevisions.UpdatedBy
AND OPG_Employees.EmployeeID = CourseRevisions.ApprovedBy
AND OPG_Employees.EmployeeID = CourseRevisions.RequestedBy
This only returns a single result which just happens to have the same employee ID listed for all 3 (Requested, Approved and Updated)
How would i get it so I can get the table result for individual employees in each?
You have to join to the OPG_Employees table once for each field, i.e. 3 times in the example above. One INNER JOIN to it for UpdatedBy, one INNER JOIN for ApprovedBy, one INNER JOIN for RequestedBy.
Something like so:
SELECT *
FROM Courses
INNER JOIN CourseRevisions ON CourseRevisions.PELID = Courses.PELID
INNER JOIN CourseGroups ON CourseGroups.CourseGroupID = Courses.CourseGroupID
INNER JOIN [dbo].[OPG_Employees] empUpdatedBy ON empUpdatedBy.EmployeeID = CourseRevisions.UpdatedBy
INNER JOIN [dbo].[OPG_Employees] empApprovedBy ON empApprovedBy.EmployeeID = CourseRevisions.ApprovedBy
INNER JOIN [dbo].[OPG_Employees] empRequestedBy ON empRequestedBy.EmployeeID = CourseRevisions.RequestedBy
You need a separate join for each employee being referenced:
SELECT *
FROM Courses INNER JOIN
CourseRevisions
ON CourseRevisions.PELID = Courses.PELID INNER JOIN
CourseGroups
ON CourseGroups.CourseGroupID = Courses.CourseGroupID INNER JOIN
[dbo].[OPG_Employees] UpdateEmp
ON UpdateEmp.EmployeeID = CourseRevisions.UpdatedBy INNER JOIN
[dbo].[OPG_Employees] ApprovedEmp
on OPG_ApprovedEmp.EmployeeID = CourseRevisions.ApprovedBy INNER JOIN
[dbo].[OPG_Employees] RequestedEmp
on RequestedEmp.EmployeeID = CourseRevisions.RequestedBy
Your original formulation required that all three ids be exactly the same.