Filter on second left join - SQL - mysql

I have three tables. One consists of customers, one consists of products they have purchased and the last one of the returns they have done:
Table customer
CustID, Name
1, Tom
2, Lisa
3, Fred
Table product
CustID, Item
1, Toaster
1, Breadbox
2, Toaster
3, Toaster
Table Returns
CustID, Date, Reason
1, 2014, Guarantee
2, 2013, Guarantee
2, 2014, Guarantee
3, 2015, Guarantee
I would like to get all the customers that bought a Toaster, unless they also bought a breadbox, but not if they have returned a product more than once.
So I have tried the following:
SELECT * FROM Customer
LEFT JOIN Product ON Customer.CustID=Product.CustID
LEFT JOIN Returns ON Customer.CustID=Returns.CustID
WHERE Item = 'Toaster'
AND Customer.CustID NOT IN (
Select CustID FROM Product Where Item = 'Breadbox'
)
That gives me the ones that have bought a Toaster but not a breadbox. Hence, Lisa and Fred.
But I suspect Lisa to break the products on purpose, so I do not want to include the ones that have returned a product more than once. Hence, what do I add to the statement to only get Freds information?

How about
SELECT * FROM Customer
LEFT JOIN Product ON Customer.CustID=Product.CustID
WHERE Item = 'Toaster'
AND Customer.CustID NOT IN (
Select CustID FROM Product Where Item = 'Breadbox'
)
AND (SELECT COUNT(*) FROM Returns WHERE Customer.CustId = Returns.CustID) <= 1

The filter condition goes in the ON clause for all but the first table (in a series of LEFT JOIN:
SELECT *
FROM Customer c LEFT JOIN
Product p
ON c.CustID = p.CustID AND p.Item = 'Toaster' LEFT JOIN
Returns r
ON c.CustID = r.CustID
WHERE c.CustID NOT IN (Select p.CustID FROM Product p Where p.Item = 'Breadbox');
Conditions on the first table remain in the WHERE clause.
As a note: A table called Product that contains a CustId seems awkward. The table behaves more likes its name should CustomerProducts.

You use conditional COUNT
SELECT C.CustID, C.Name
FROM Customer C
JOIN ( SELECT CustID
FROM Products
GROUP BY CustID
HAVING COUNT(CASE WHEN Item = 'Toaster' THEN 1 END) > 1
AND COUNT(CASE WHEN item = 'Breadbox' THEN 1 END) = 0
) P -- Get customer with at least one Toaster and not Breadbox
ON C.CustID = P.CustID
JOIN ( SELECT CustID
FROM Returns
HAVING COUNT(*) < 2
) R -- Get only customers with less than 2 returns
ON C.CustID = R.CustID

Related

"INNER JOIN" and "LEFT JOIN" with "GROUP BY"

My tables looks like this
sales
----------------------------------------------------------
id ordernumber quantity category_id price
1 402-9182243-8008368 1 3 22.95
2 406-3666671-8627555 2 3 6.95
3 303-1935495-5532309 1 1 7.95
4 171-5799800-1198702 1 2 159.95
5 403-2398078-4901169 2 2 18.95
category
--------------
id name
1 bikes
2 shoes
3 planes
returns
--------------
id ordernumber quantity costs
1 402-9182243-8008368 1 22.95
2 402-9182243-8008368 5.95 // return shipping fee
And here is my query
SELECT c.name,
SUM(v.quantity) AS sold, # wrong
SUM(s.quantity * s.price) AS turnover, # wrong
SUM(r.costs) AS returncosts,
FROM sales AS s
INNER JOIN categories AS c ON c.id = s.category_id
LEFT JOIN returns AS r ON r.ordernumber = s.ordernumber
GROUP BY c.name
I have some inner joins with aggregate functions.
But I also need "return" with a "Left Join" (I think).
And with Left Join, my aggregate functions dont work anymore.
Left Join adds additional rows. Additional data, for sum().
I need a single query, so every column is sortable later.
I would be happy about any help. Best Regards
It's a semi cartesian product because ordernumber is not unique in returns table.
We can see what's happening if we remove the aggregate functions and return the detail rows.
One possible approach is to pre-aggregate returns in an inline view, so that unique values of ordernumber are returned.
Assuming ordernumber is unique in sales table, then something like this:
SELECT c.name
, SUM(s.quantity) AS sold
, SUM(s.quantity * s.price) AS turnover
, SUM(r.returncosts) AS returncosts
FROM sales s
JOIN categories c
ON c.id = s.category_id
LEFT
JOIN ( SELECT t.ordernumber
, SUM(t.costs) AS returncosts
FROM returns t
GROUP
BY t.ordernumber
) r
ON r.ordernumber = s.ordernumber
GROUP
BY c.name
You can sum the quantity separately from the LEFT JOIN in a sub query as follows:
SELECT t1.name, t1.sold, t1.turnover, SUM(r.costs) AS returncosts
FROM(
SELECT c.name,
SUM(s.quantity) AS sold,
SUM(s.quantity * s.price) AS turnover
FROM sales AS s
INNER JOIN categories AS c ON c.id = s.category_id
GROUP BY name
) t1
LEFT JOIN returns AS r ON r.ordernumber = s.ordernumber
GROUP BY t1.name, t1.sold, t1.turnover

Mysql - join two tables without losing relevant values [duplicate]

I have two tables representing a database for customer products and its competitors' products:
tmp_match - from_product_id and to_product_id representing matches between customer product and competitor product respectively.
tmp_price_history - shows the price of each product per date.
I am trying to write a query which will list all dates from table tmp_price_history. For each date I want to see customer product price vs competitor product price according to product matches pairs in table tmp_match, regardless of whether there was a price history record for customer product or competitor product or both:
if both prices are available for a specific date - list them both in their columns
if there is only a record for customer product - show only customer price (and leave the competitor column blank).
if there is only record for competitor product - show competitor price in its column.
Expected result:
date from_product_id to_product_id cust_price comp_price
1 1 11 99 95
2 1 11 98 94
1 1 12 92
2 1 12 91
2 2 108
I tried to achieve that using this query:
select cust_hist.date, from_product_id, to_product_id, cust_hist.price as cust_price,comp_hist.price as comp_price
from tmp_match as matches
left join tmp_price_history cust_hist
on cust_hist.product_id = matches.from_product_id
left join tmp_price_history comp_hist
on comp_hist.product_id = matches.to_product_id
;
but it doesn't achieve my goal as can be seen in this sql snippet.
I think that you are looking for this:
select distinct *
from (SELECT date,
if(group_concat(distinct cust_price), from_product_id, null)as from_product_id,
if(group_concat(distinct comp_price), to_product_id, null) as to_product_id,
group_concat(distinct cust_price) as cust_price,
group_concat(distinct comp_price) as comp_price
FROM (select cust_hist.date,matches.from_product_id,
matches.to_product_id,cust_hist.price cust_price,
comp_hist.price comp_price
from tmp_match matches
inner join tmp_price_history cust_hist on matches.from_product_id = cust_hist.product_id
inner join tmp_price_history comp_hist on matches.to_product_id = comp_hist.product_id
WHERE comp_hist.date = cust_hist.date
union
select comp_hist.date,matches.from_product_id,
matches.to_product_id,null as cust_price,
comp_hist.price comp_price
from tmp_price_history comp_hist
join tmp_match matches
on matches.to_product_id = comp_hist.product_id # and matches.from_product_id is null
union
select cust_hist.date,matches.from_product_id,
matches.to_product_id,
cust_hist.price cust_price,
null comp_price
from tmp_price_history cust_hist
join tmp_match matches
on matches.from_product_id = cust_hist.product_id # and matches.to_product_id is null
order by DATE, from_product_id, to_product_id, cust_price, comp_price) as u
group by date,from_product_id,to_product_id) g
Your idea about sql snippet was great!

MySQL - Marking duplicates from several table fields, as well as data from another table

I have two tables - one shows user purchases, and one shows a product id with it's corresponding product type.
My client wants to make duplicate users inactive based on last name and email address, but wants to run the query by product type (based on what type of product they purchased), and only wants to include user_ids who haven't purchased paint (product ids 5 and 6). So the query will be run multiple times - once for all people who have purchased lawnmowers, and then for all people who have purchased leafblowers etc (and there will be some overlap between these two). No user_id that has purchased paint should be made inactive.
In terms of who should stay active among the duplicates, the one to stay active will be the one with the highest product id purchased (as products are released annually). If they have multiple records with the same product id, the record to stay active will be the one with most recent d_modified and t_modified.
I also want to shift the current value of 'inactive' to the 'previously_inactive' column, so that this can be easily reversed if need be.
Here is some sample table data
If the query was run by leafblower purchases, rows 5, 6, and 7 would be made inactive. This is the expected output:
If the query was run by lawnmower purchases, rows 1 and 2 would be made inactive. This would be the expected output:
If row 4 was not the most recent, it would still not be made inactive, as user_id 888 had bought paint (and we want to exclude these user_ids from being made inactive).
This is an un-optimised version of the query for 'leafblower' purchases (it is working, but will probably be too slow in the interface):
UPDATE test.user_purchases
SET inactive = 1
WHERE id IN (
SELECT z.id
FROM (SELECT * FROM test.user_purchases) z
WHERE z.product_id IN (
SELECT product_id
FROM test.products
WHERE product_type IN ("leafblower")
)
AND id NOT IN (
SELECT a.id
FROM (SELECT * FROM test.user_purchases) a
INNER JOIN (
SELECT r.surname, r.email
FROM (SELECT * FROM test.user_purchases) r
JOIN test.products s on r.product_id = s.product_id
WHERE s.product_type IN ("paint")
) b
WHERE a.surname = b.surname
AND a.email = b.email
)
AND id NOT IN (
SELECT MAX(z.id)
FROM (SELECT * FROM test.user_purchases) z
WHERE z.product_id IN (
SELECT product_id
FROM test.products
WHERE product_type IN ("leafblower")
)
AND id NOT IN (
SELECT a.id
FROM (SELECT * FROM test.user_purchases) a
INNER JOIN (
SELECT r.surname, r.email
FROM (SELECT * FROM test.user_purchases) r
JOIN test.products s on r.product_id = s.product_id
WHERE s.product_type IN ("paint")
) b
WHERE a.surname = b.surname
AND a.email = b.email
)
GROUP BY surname, email
)
)
Any suggestions on how I can streamline this query and optimise the speed of it would be much appreciated.

MySQL - At least one of something and greater than between 3 tables

I have 3 tables :
I need to retrieve the name of the sellers that have sold during January AT LEAST one product where the total amount of sales of this said product during January is greater than 1000.
I thought about starting like this :
SELECT c.nom, c.prenom
FROM Commerciaux c
LEFT JOIN Ventes v ON c.id_commerciaux = v.id_commerciaux
WHERE EXISTS (SELECT 1
FROM Produits p, Ventes v
WHERE p.id_produits = v.id_produits
AND MONTH(v.date) = 1
GROUP BY p.nom
HAVING SUM(v.montant) > 200)
AND MONTH(v.date) = 1
GROUP BY c.nom, c.prenom
The SELECT in the WHERE EXISTS seems to be working, but when I have to get link the table Sellers, I don't know how to write it.
Any help would be great !
You can use something like this:
select distinct s.name
from Sellers s,
( -- get all those products that qualify (more than 1000 sales)
select product_id, sum(amount) as total
from Sales
where Month(date) = 1
group by product_id
having total > 1000
) vp,
Sales sa
where
s.id = sa.commercial_id and
sa.product_id = vp.id and
Month(sa.date) = 1
#Leo answer would return only those Sellers that have sold more than 100 of the product in January instead of returning all Sellers that have sold any amount of the product that has been sold in an amount greater than 1000 across the board.
You could use a nested query:
SELECT s.name FROM
(Sellers as s JOIN Sales as sp on sp.commercial_id = s.id) JOIN
Product as p on p.id = sp.product_id
WHERE MONTH(sp.date) = 1 AND YEAR(sp.date) = 2017 AND
(SELECT SUM(Amount) FROM Sales as sp2 WHERE sp2.commercial_id = s.id
AND sp2.product_id = p.id
AND MONTH(sp2.date) = 1 AND YEAR(sp2.date) = 2017) > 1000
First, join the three tables on foreign keys, and then run a nested query to compute amount of the selected seller on a specific product to check to pass the amount limitation.
try this
SELECT C.name
FROM Sales A
JOIN Product B ON A.produit_id = B.id
JOIN Seller C ON A.commercial_id = C.id
WHERE MONTH(A.date) = 1
HAVING SUM(A.nAmount) > 100
GROUP BY C.name

Getting data from multiple tables in sql

Table 1: Invoices (inv_id, inv_value, cust_id)
Table 2: Customers (cust_id, sales_rep)
Table 3: Members (Member_id, member_cateogry, member_type, cust_id)
Note 1: Each Customer Pays multiple Invoices. (One-to-Many Relationship).
Note 2: Each Customer pays for one-or-more members (so more than one member could be related to one customer).
Note 3: Each Member has a category which could be 1 "represents Individual" OR 2 "represents Group".
Note 4: Each Member has a type which could be 1 "represents new" OR 2 "represents renew".
I want to get the TOTAL of the Invoice_value field for customers who's sales_rep = 1 and their member_category = 10 and their members_type = 123
Ex: What is the total amount of Invoices that customers paid IF the Sales_rep for these customers was 1 and the members they paid for were new and Individual members.
I tried:
SELECT Sum(invoices.inv_value) AS total
FROM invoices,
customers,
members
WHERE invoices.cust_id = customers.cust_id
AND members.custid = customers.cust_id
AND members.category = {$category}
AND members_type = {$type}
AND customers.sales_rep = {$id}";
AND
SELECT Sum(invoices.inv_value) AS total
FROM members
INNER JOIN customers
ON members.custid = customers.cust_id
INNER JOIN invoices
ON customers.cust_id = invoices.cust_id
WHERE customers.sales_rep = {$id}
AND members.category = {$category}
AND members.type = {$type}";
But both return double the Invoice value.
ex.: 1 Invoice for $120 in the Invoices table return $240 using these sql queries.
How can I fix this?
This is your query:
SELECT sum(i.inv_value) as total
FROM members m INNER JOIN
customers c
ON m.custid = c.cust_id INNER JOIN
invoices i
ON c.cust_id = i.cust_id
WHERE c.sales_rep = {$id} AND
m.category = {$category} AND
m.type = {$type}";
(Don't use implicit JOIN syntax using commas. It is archaic and less powerful.)
The problem is probably that two members can have the same customer id. You can check this by running:
select m.cust_id, count(*)
from members m
group by m.cust_id
having count(*) > 1;
It is also possible that customer ids are duplicated in customers.
Assuming the duplicates are only in members, change the query to exists:
SELECT sum(i.inv_value) as total
FROM customers c
ON INNER JOIN
invoices i
ON c.cust_id = i.cust_id
WHERE c.sales_rep = {$id} AND
EXISTS (SELECT 1
FROM members m
WHERE m.custid = c.cust_id AND
m.category = {$category} AND
m.type = {$type}
);
It seems like you are taking inv_value from invoices, which has many to one relationship with customers but customer table and members table have one to many relationship.
Say you have below data
Invoice Table
invoices.cust_id invoices.inv_value
custid1 100
Customer table
customer.cust_id
custid1
Members Table
members.cust_id members.category
custid1 1
custid1 2
On join all three tables
customer.cust_id members.cust_id invoices.inv_value members.category
custid1 custid1 100 1
custid1 custid1 100 2
if you notice as custid1 exist in 2 member_category, invoice value is also duplicated.
To solve this, first you can take the distinct records, then summing those distinct records would help you solve your problem as below
Solution
SELECT Sum(invoices.inv_value) AS total
FROM(
SELECT DISTINCT members.custid, invoices.inv_value inv_value
FROM members
INNER JOIN customers
ON members.custid = customers.cust_id
INNER JOIN invoices
ON customers.cust_id = invoices.cust_id
WHERE customers.sales_rep = {$id}
AND members.category = {$category}
AND members.type = {$type});