Joining two tables with a queried table - sql

Oh great SQL gods I require your assistance.
Here is my Schema:
CAR(Serial_no,Model,Manufacturer,Price)
OPTIONS(Serial_no,Option_name,Price)
SALE(Salesperson_id,Serial_no,Date,Sale_price)
SALESPERSON(Salesperson_id,Name,Phone)
First, I need to join the CAR and SALE table by Serial_no.
Second, i need to take the OPTIONS table and SUM all the prices for similar Serial_no which the following does:
SELECT O.Serial_no, SUM(O.Price)
FROM OPTIONS O
GROUP BY (O.Serial_no);
Last I need to merge steps one and two and query the result so I get a resulting set of where CAR.Price < (SALE.Sale_price + OPTIONS.Price).
Can this be done? Any help would be immensely appreciated!
Thanks,
Mark

SELECT C.Serial_no,
MIN(c.Price) CarPrice,
MIN(s.Sale_price) SalePrice,
SUM(o.Price) OptionsPrice,
MIN(s.Sale_price) + IFNULL(SUM(o.Price),0) TotalPrice
FROM Car c JOIN Sale s ON c.Serial_no = s.Serial_no
LEFT JOIN `Options` o ON c.Serial_no = o.Serial_no
GROUP BY c.Serial_no
HAVING MIN(c.Price) < MIN(s.Sale_price) + IFNULL(SUM(o.Price),0)
Note: the MIN() are not taking anything away, it is only there since you are grouping, and the options table may have multiple rows.
Another option would be to do the calculations in a Subquery which may lead to better performance:
SELECT C.Serial_no,
C.Price,
S.Sale_price,
og.SumPrice
FROM Car c JOIN Sale s ON c.Serial_no = s.Serial_no
LEFT JOIN (
SELECT Serial_no, SUM(Price) SumPrice
FROM `Options`
GROUP BY Serial_no
) og ON c.Serial_no = og.Serial_no
WHERE c.Price < s.Sale_price + IFNULL(og.SumPrice,0)

Related

Multi Join Table, Multiple Sums

I've got 3 tables I need to work with:
CREATE TABLE invoices (
id INTEGER,
number VARCHAR(256)
)
CREATE TABLE items (
invoice_id INTEGER,
total DECIMAL
)
CREATE TABLE payments (
invoice_id INTEGER,
total DECIMAL
)
I need a result set along the lines of:
invoices.id
invoices.number
item_total
payment_total
oustanding_balance
00001
i82
42.50
42.50
00.00
00002
i83
89.99
9.99
80.00
I tried
SELECT
invoices.*,
SUM(items.total) AS item_total,
SUM(payments.total) AS payment_total,
SUM(items.total) - SUM(payments.total) AS oustanding_balance
FROM
invoices
LEFT OUTER JOIN items ON items.invoice_id = invoices.id
LEFT OUTER JOIN payments ON payments.invoice_id = invoices.id
GROUP BY
invoices.id
But that fails. The sum for payments ends up wrong since I'm doing 2 joins here and I end up counting payments multiple times.
I ended up with
SELECT
invoices.*,
invoices.item_total - invoices.payment_total AS oustanding_balance
FROM
(
SELECT invoices.*,
(SELECT SUM(items.total FROM items WHERE items.invoice_id = invoices.id) AS item_total,
(SELECT SUM(payments.total FROM payments WHERE payments.invoice_id = invoices.id) AS payment_total
) AS invoices
But ... that feels ugly. Now I've got subqueries going on everywhere. It DOES work, but I'm concerned about performance?
There has to be some good way to do this with joins - I'm sure I'm missing something super obvious?
As you say the sum behavior with multiple joins is normal and working with sub queries (Or CTE for SQl Server) is not a bad practice.
Doing such GOUP BY on an ID and a total in sub queries won't significantly downgrade your performance (depending on your tables sizes).
Another solution could be doing one SUM sub query for each column you need. It would be easier to understand this way I think :
SELECT
invoices.id
, i_total.total as item_total
, p_total.total aspayment_total
, ( i_total.total - p_total.total) as outstanding_balance
FROM
invoices
LEFT JOIN (
SELECT invoice_id, SUM(total) as total FROM items GROUP BY invoice_id
) i_total
ON i_total.invoice_id = invoices.id
LEFT JOIN (
SELECT invoice_id, SUM(total) as total FROM payments GROUP BY invoice_id
) p_total
ON p_total.invoice_id = invoices.id
I think a common table expression (or in this case two CTEs) will give you what you want. You are using something called a scalar, which is precisely speaking not wrong, but as you correctly identified is ugly, hard to read, hard to maintain and can be non-performant in many situations.
CTE essentially take a query and makes it "behave" like a table. We define it once and then we can refer to it later.
with item_data as (
SELECT invoice_id, SUM(total) as item_total
FROM items
group by invoice_id
),
payment_data as (
SELECT invoice_id, SUM(total) as payment_total
FROM payments
group by invoice_id
)
select
i.*,
id.item_total - pd.payment_total as outstanding_balance
from
invoices i
join item_data id on i.invoice_id = id.invoice_id
join payment_data pd on i.invoice_id = pd.invoice_id
Untested, but hopefully you get the idea.

sql multiple left joins with sum

I have 3 tables as below. What I need to do is create a sumamry after left joining the 1st table to the 2nd and the 2nd to the 3rd.
The code I'm using ends up resulting in a cartesian join. My query to create the 1st table (person) is complicated and resource intensive while the volume of data is table 2(shopping list) is massive so having a nested query is not ideal. Below is the code I'm using right now and the expected output (image 1) & what I get (image 2)
select
a.ID,
a.Name,
sum(b.cost) total_cost,
sum(c.discount_amount) total_discount
from
person a,
left join shopping_list b on a.id=b.id
left join discount c on b.item = c.item
group by
a.ID,
a.Name
I've looked at the below links but I was hoping there's a solution that may work better give the size of my dataset
https://dba.stackexchange.com/questions/217220/how-i-use-multiple-sum-with-multiple-left-joins
Multiple Left Join with sum
Thanks in advance for your help
You have multiple rows for the discounts, so presummarize those:
select p.id, p.name, coalesce(sl.cost, 0) as cost,
coalesce(d.discount_amount, 0) as discount_amount
from person p left join
shopping_list sl
on sl.id = p.id left join
(select d.item, sum(discount_amount) as discount_amount
from discount
group by d.item
) d
on sl.item = d.item
group by p.id, p.name;
The problem with your query is that the multiple rows of discount end up multiplying the rows of shopping_list -- resulting in the inaccurate totals.
Notice that in this query, the table aliases are abbreviations for the table names. This is a best practice that makes it much, much easier to follow the logic of a query.

Define sort order when updating

I have a script that updates an ID field on one table where that record matches to another table based on criteria.
Below is the general structure of my query.
update p.saleId = e.saleId
from products p inner join sales s on s.crit1 = p.crit1
where p.someDate between s.startDate and s.endDate
This is working fine. My issue is that in some situations there is more than one match on the 'sales' table with this query which is generally ok. I'd however like to sort these results based on another field to make sure the saleId I get is the one with the highest cost.
Is that possible?
As it is the saleID you want to set and the sales table you are looking up, you can probably just update all products records. Then you can write a simple update statement on the table and don't have to join. This makes this much easier to write:
update products p
set saleId =
(
select top(1) s.saleId
from sales s
where s.crit1 = p.crit1
and p.someDate between s.startDate and s.endDate
order by cost desc
);
The main difference to your statement is that mine sets saleId = NULL where there is no match in the sales table, while your lets these untouched. But I guess that doesn't make a difference here.
I hope the below query may solve. Wrote very high level draft as per your question. Please take only the concept not the syntax.
with maxSales as (select salesId, crit1 from sales s1
where cost = (select max(cost) from
sales s2 where s1.crit1 = s2.crit1)
update products p set p.saleId =
(select s.saleId from
maxSales s
where s.crit1 = p.crit1
and p.someDate between s.startDate and s.endDate)
UPDATE p
set p.saleId = e.rowNumber
FROM products p
INNER JOIN
(SELECT saleId, row_number() OVER (ORDER BY saleId DESC) as rowNumber
FROM sales)
e ON e.saleId = p.saleId
TRY THIS:
UPDATE p
SET p.saleid = s.saleid
FROM products p
INNER JOIN
(SELECT s.crit1,
s.saleid
FROM sales s
WHERE cost IN
(SELECT max(cost) cost
FROM sales
GROUP BY crit1)) s ON s.crit1 = p.crit1
None of the answers worked, but I managed to do it by using and Outer Apply as my join, and specified the sort order in that.
Cheers everyone for the input.

Basic SQL Joining Tables

Having a bit of trouble with a basic SQL problem.
The question is that I have to find the salespersons first and last name, then their Social Insurance Number, the product description, the product price, and quantity sold where the total quantity sold is greater than 5.
I'll attach the database information below as a photo.
Product quantity sold greater than 5
SELECT ProductId
FROM ProductsSales
HAVING SUM(QuantitySold) > 5
Use that to get the rest:
SELECT s.FirstName, s.LastName, s.SIN, p.ProductDescription, ps.UnitSalesPrice, ps.QuantitySold
FROM ProductsSales ps
LEFT JOIN Products p on p.ProductID = ps.ProductID
LEFT JOIN Salesmen s on s.SalesmaneID = ps.SellerID
WHERE ps.ProductID IN
(
SELECT ProductId
FROM ProductsSales
GROUP BY ProductId
HAVING SUM(QuantitySold) > 5
)
SELECT a.FirstName, a.LastName, a.SIN, c.ProductDescription, b.UnitSalesPrice, b.QuantitySold
FROM Salesmen a
LEFT JOIN ProductsSales b
ON a.SalesmanId = b.SellerId
LEFT JOIN Products c
ON b.ProductId = c.ProductId
WHERE b.QuantitySold > 5
Select a.FirstName, a.LastName, a.SIN From Salesmen as a,
c.ProductDescriptio, c.Price, b.sum(QunatitySold)
inner join ProductSales as b on a.Salesmanid = b.sellerid
inner join Products as c on c.ProductId = b.ProductId
having b.sum(QunatitySold)> 5
group by a.FirstName, b.ProductDescription
Brad,
Welcome to SQL. Joining for me was a terrifying experience when I first started but its really easy. The general concept is this:
Pick a Join
If you want to see all records that would be common between the two table, you would use and JOIN. If you wanted to combine the two tables but still show all records you use LEFT JOIN
The basic syntax is
SELECT fieldnames FROM tablename alias
JOIN othertable alias ON firstalias.field = secondalias.field
--Example
SELECT animal, food, idtag from animals a
JOIN food f on a.animalid = f.animalid
This assumes you have a common field animalid in both the animals table and the food table. you should also ideally preface the field names with the alias to make it easier to understand like this: a.animal, f.food
And you keep going until you have joined all the tables you need.
Make sure you only request field names you want
Hope that helps

Join two tables where all child records of first table match all child records of second table

I have four tables: Customer, CustomerCategory, Limit, and LimitCategory. A customer can be in multiple categories and a limit can also have multiple categories. I need to write a query that will return the customer name and limit amount where ALL the customers categories match ALL the limit categories.
I'm guessing it would be similar to the answer here, but I can't seem to get it right. Thanks!
Edit - Here's what the tables look like:
tblCustomer
customerId
name
tblCustomerCategory
customerId
categoryId
tblLimit
limitId
limit
tblLimitCategory
limitId
categoryId
I THINK you're looking for:
SELECT *
FROM CustomerCategory
LEFT OUTER JOIN Customer
ON CustomerCategory.CustomerId = Customer.Id
INNER JOIN LimitCategory
ON CustomerCategory.CategoryId = LimitCategory.CategoryId
LEFT OUTER JOIN Limit
ON Limit.Id = LimitCategory.LimitId
Updated!
Thanks to Felix for pointing out a flaw in my existing solution (3 years after I originally posted it, hehe). After looking at it again, I think this might be correct. Here I'm getting (1) the customers and limits with matching categories, plus the number of matching categories, (2) the number of categories per customer, (3) the number of categories per limit, (4) I then ensure the number of categories for customer and limits is the same as the number of the matches between the customers and limits:
UNTESTED!
select
matches.name,
matches.limit
from (
select
c.name,
c.customerId,
l.limit,
l.limitId,
count(*) over(partition by cc.customerId, lc.limitId) as matchCount
from tblCustomer c
join tblCustomerCategory cc on c.customerId = cc.customerId
join tblLimitCategory lc on cc.categoryId = lc.categoryId
join tblLimit l on lc.limitId = l.limitId
) as matches
join (
select
cc.customerId,
count(*) as categoryCount
from tblCustomerCategory cc
group by cc.customerId
) as customerCategories
on matches.customerId = customerCategories.customerId
join (
select
lc.limitId,
count(*) as categoryCount
from tblLimitCategory lc
group by lc.limitId
) as limitCategories
on matches.limitId = limitCategories.limitId
where matches.matchCount = customerCategories.categoryCount
and matches.matchCount = limitCategories.categoryCount
I don't know if this will work or not, just a thought i had and i can't test it, I'm sures theres a nicer way! don't be too harsh :)
SELECT
c.customerId
, l.limitId
FROM
tblCustomer c
CROSS JOIN
tblLimit l
WHERE NOT EXISTS
(
SELECT
lc.limitId
FROM
tblLimitCategory lc
WHERE
lc.limitId = l.id
EXCEPT
SELECT
cc.categoryId
FROM
tblCustomerCategory cc
WHERE
cc.customerId = l.id
)