Exclusive mysql select query, two tables - mysql

I have the following tables (they all got more columns but I'm just showing the ones of interest):
Product Order details Orders
---------------------------- ---------------------------- --------------
| id_product | id_supplier | | id_order | id_product | | id_order |
| 12 | 2 | | 1 | 56 | | 1 |
| 32 | 4 | | 2 | 32 | | 2 |
| 56 | 2 | | 2 | 56 | | 3 |
| 10 | 1 | | 4 | 56 | | 4 |
---------------------------- | 3 | 12 | --------------
----------------------------
What I want to do is select all orders which have products from ONLY one or more suppliers. So lets say I want all orders that only have products from the supplier with id 2 (id_supplier = 2) I should get the orders with id 1, 3 and 4.
If I want all orders that ONLY have products from the supplier with id 4 (id_supplier = 4) I should get an empty result.
If I want all orders that ONLY have products from the suppliers with id 2 AND 4 I should get the order with id 2.
I've read the following question: mySQL exclusive records but I can't get a grip of that query to work when I have two tables like I have. I just need another pair of eyes to help me out here! :)
Do you have any idea on how I'll do this?
EDIT: To clearify, I want to fetch all orders that ONLY contains products from one or more specified suppliers. Orders with products from other suppliers than is specified, should not be included.

per the questions I've listed, I think THIS is what you want, and can be done with a LEFT join.
select
od.id_order,
sum( if( p.id_supplier in ( 2, 4 ), 1, 0 )) as HasSupplierLookingFor,
sum( if( p.id_supplier in ( 2, 4 ), 0, 1 )) as HasOtherSuppliers
from
order_Details od
join product p
on od.id_product = p.id_product
group by
od.id_order
having
HasSupplierLookingFor > 0
AND HasOtherSuppliers = 0
Sometimes, just answering a question that can be somewhat ambiguous as presented leads to misrepresented answers. This query will by a per order basis, join to the products to find the suppliers and group by the order id.
For each product ordered, the first SUM() asks if its one of the suppliers you ARE looking for, if so, sum a value of 1, otherwise 0... The next SUM() asks the same thing... but if it IS the supplier, use zero, thus all OTHER suppliers gets the 1.
So, now, the HAVING clause is looking for any order that at a minimum of 1 of your suppliers qualified AND it had no other suppliers represented.
So you could have an order with 30 items, and 20 from supplier 2, and 10 from supplier 4. The HasSupplierLookingFor would = 30, and HasOtherSuppliers = 0, the order would be included.
Another order could have 5 items. One from supplier 2, and 4 others from supplier 9. This would have HasSupplierLookingFor = 1, and HasOtherSuppliers = 4, thus exclude this as a qualified order.

You should inner join all those tables, like this:
SELECT o.* from Orders o
INNER JOIN Details d ON o.id_order = d.id_order
INNER JOIN Products p ON d.id_product = p.id_product
WHERE p.id_supplier = 4
That will give you the orders which include products from that supplier.

SELECT o.id_order
FROM Orders o
INNER JOIN `Order details` od
ON o.id_order = od.id_order
INNER JOIN Product p
ON p.id_product = od.id_product
WHERE p.id_supplier IN (2,4)
the (2,4) are the suppliers you want to fetch. you can also ask for only 1 by saying (2)

Related

How to group by count of related records

I've two tables invoices and products.
invoices: store,
products: id, invoice_id
I want to have a result set that shows how many invoices exists for each quantity of products.
I mean, if I have 2 invoices with 3 products each on store A, it will show Store: A, Products qty: 3, Number of invoices (with three products): 2
Another example:
| store | products_qty | count |
| A | 1 | 10 |
| A | 2 | 7 |
| A | 5 | 12 |
| B | 5 | 12 |
Meaning, store A has 10 invoices with 1 product. 7 with 2 products, and 12 with 5 products...
I've tried with something like:
SELECT store, count(p.id), count(i.id) FROM invoices i
LEFT JOIN products p ON (p.invoice_id = i.id)
GROUP BY price, count(i.id)
however my group cause is not valid, it shows Invalid use of group function.
How can I accomplish this?
I was able to do using subqueries, I wonder if is possible without it:
SELECT store, products_qty, count(*) FROM (
SELECT store, count(p.id) as products_qty, count(i.id) as invoices_count
FROM invoices i
LEFT JOIN products p ON (p.invoice_id = i.id)
GROUP BY price, i.id
) AS temp GROUP BY store, products_qty;

How to use LEFT OUTER JOIN to identify missing products by vendor?

This query has been fun to figure out but I have come to place where I need some help.
I have several tables and the ultimate question is:
How many total parts are "missing", by vendor?
and / or
How many total parts are "missing", by vendor and category?
Missing: has not been utilized by the vendor (see query 1).
Note that parts are not attributed to a product or a vendor because both of those could change based on the season and often the parts inspire what the product will actually be.
Very basically, which part each vendor be aware of is the question we are trying to answer on a high level to determine which vendors have the most missing parts in which categories are those parts missing?
Now, I do have the first query I need working great. What it does is tell me the missing parts by category when I specify the specific vendor.
Here is the SQLfiddle for both the create script for the database and the working query:
Query 1:
http://sqlfiddle.com/#!9/088e7/1
And the query:
SELECT
c.name AS category,
COUNT(pt.id) AS parts,
COUNT(CASE WHEN in_stock IS NULL THEN pt.id END) AS missing_parts
FROM
season AS s
LEFT OUTER JOIN
(
SELECT
s.id AS season_id,
s.type season_type,
max(i.in_stock) AS in_stock
FROM
inventory AS i
JOIN season AS s ON i.season_id = s.id
JOIN product AS p ON i.product_id = p.id
JOIN vendor AS v ON p.vendor_id = v.id
JOIN part AS pt ON s.part_id = pt.id
WHERE
v.id = 2
AND
s.type = 'Type A'
GROUP BY
1,2) AS seas ON seas.season_id = s.id AND seas.season_type = s.type
JOIN part AS pt ON pt.id = s.part_id
JOIN part_data AS pd ON pt.id = pd.part_id
JOIN category AS c ON pt.category_id = c.id
WHERE
s.type = 'Type A'
GROUP BY
1;
The above works like a charm and here are the results:
| name | parts | missing_parts |
|-----------|-------|---------------|
| category3 | 3 | 2 |
| category4 | 2 | 0 |
| category5 | 2 | 2 |
| category6 | 3 | 3 |
My problem is when I try to do a similar query using vendor instead of category at the same time removing the vendor filter. In the following SQL fiddle, you can see that because the parts are in fact missing they of course cannot be attributed to a vendor when querying like I am.
http://sqlfiddle.com/#!9/088e7/2
And them Query 2:
SELECT
seas.vendor AS vendor,
COUNT(pt.id) AS parts,
COUNT(CASE WHEN in_stock IS NULL THEN pt.id END) AS missing_parts
FROM
season AS s
LEFT OUTER JOIN
(SELECT
s.id AS season_id,
v.name AS vendor,
s.type season_type,
max(i.in_stock) AS in_stock
FROM
inventory AS i
JOIN season AS s ON i.season_id = s.id
JOIN product AS p ON i.product_id = p.id
JOIN vendor AS v ON p.vendor_id = v.id
JOIN part AS pt ON s.part_id = pt.id
WHERE
s.type = 'Type A'
GROUP BY
1,2 ) AS seas ON seas.season_id = s.id AND seas.season_type = s.type
JOIN part AS pt ON pt.id = s.part_id
JOIN part_data AS pd ON pt.id = pd.part_id
JOIN category AS c ON pt.category_id = c.id
AND
s.type = 'Type A'
GROUP BY
1;
The results from query 2:
| vendor | parts | missing_parts |
|----------|-------|---------------|
| (null) | 4 | 4 |
| Vendor 1 | 2 | 0 |
| Vendor 2 | 3 | 0 |
| Vendor 3 | 2 | 0 |
| Vendor 4 | 2 | 0 |
| Vendor 5 | 2 | 0 |
Note the null value which makes sense as those are the "missing" parts I am looking for that cannot be attributed to a Vendor.
What I am wondering is if there is anyway to have the missing part count added to an additional column?
The missing parts column in the desired output is a hard to get accurate because again and thats very point of this query, I don't know...even with this tiny amount of data. Note again, the missing parts do not have vendors but here is my best shot.
| vendor | parts | missing_parts |
|----------|-------|---------------|
| Vendor 1 | 2 | 1 |
| Vendor 2 | 3 | 1 |
| Vendor 3 | 2 | 3 |
| Vendor 4 | 2 | 0 |
| Vendor 5 | 2 | 2 |
In an ideal world I would be able to also add category:
| category | vendor | parts | missing_parts |
|------------|----------|-------|---------------|
| category 1 | Vendor 1 | 2 | 1 |
| category 1 | Vendor 2 | 3 | 1 |
| category 1 | Vendor 3 | 2 | 3 |
| category 1 | Vendor 4 | 2 | 0 |
| category 1 | Vendor 5 | 2 | 2 |
| category 2 | Vendor 1 | 1 | 1 |
| category 2 | Vendor 2 | 1 | 1 |
| category 2 | Vendor 3 | 0 | 3 |
| category 2 | Vendor 4 | 2 | 0 |
| category 2 | Vendor 5 | 0 | 2 |
IF I am understanding what you are looking for, I would first start with what you are ultimately looking for..
A list of distinct parts and categories. THEN you are looking for who is missing what. To do so, this is basically a Cartesian of every vendor against this "master list of parts/categories" and who does/not have it.
SELECT DISTINCT
pt.id,
pt.category_id
from
part pt
Now, consider the second part. What are all the possible parts and categories a specific VENDOR has.
SELECT DISTINCT
pt.id,
pt.category_id,
p.vendor_id
FROM
season s
JOIN inventory i
ON s.id = i.season_id
JOIN product p
ON i.product_id = p.id
JOIN part pt
ON s.part_id = pt.id
In the above tables, I did not need the category or actual vendor tables joined as I only cared about the qualifying IDs of who has what. First, all possible part ID and category ID, but in the second, we also grab the VENDOR ID who has it.
Now, tie the pieces together starting with the vendor JOINED to category without any "ON" condition. The join is needed to allow the "v.id" as a lower join in the syntax this will give me a Cartesian of every vendor applied / tested to every category. Then, the category table joined to all the distinct parts and finally LEFT-JOINED to the distinct parts query PER VENDOR
Finally, add your aggregates and group by. Due to the left-join, if there IS an VndParts.ID, then the record DOES exist, thus Vendor Parts FOUND count is up. If the vendor parts id is NULL, then it is missing (hence my sum case/when) for the missing parts count.
SELECT
v.name Vendor,
c.name category,
count( PQParts.ID ) TotalAvailableParts,
count( VndParts.ID ) VendorParts,
sum( case when VndParts.ID IS NULL then 1 else 0 end ) MissingParts
from
vendor v JOIN
category c
JOIN
( SELECT DISTINCT
pt.id,
pt.category_id
from
part pt ) PQParts
ON c.id = PQParts.category_id
LEFT JOIN
( SELECT DISTINCT
pt.id,
pt.category_id,
p.vendor_id
FROM
season s
JOIN inventory i
ON s.id = i.season_id
JOIN product p
ON i.product_id = p.id
JOIN part pt
ON s.part_id = pt.id ) VndParts
ON v.id = VndParts.vendor_id
AND PQParts.ID = VndParts.ID
AND PQParts.Category_ID = VndParts.Category_ID
group by
v.name,
c.name
Applied against your SQL-Fiddle sample database construct
Now, even though you have created sample data of categories 1-6, all of your PARTS are only defined with categories 3-6 as in my sample data result. I can't force for data that does not exist per the sample query of
SELECT
*
from
category c
JOIN
( SELECT DISTINCT
pt.id,
pt.category_id
from
part pt ) PQParts
ON c.id = PQParts.category_id
If such actual data DID exist, then those missing pieces of other categories would also be displayed.
Now final note. You were also looking for a specific SEASON. I would just add a WHERE clause to accommodate that in the VndParts query. Then change PQParts query to include the season join such as
SELECT DISTINCT
pt.id,
pt.category_id
from
part pt
Now, consider the second part. What are all the possible parts and categories a specific VENDOR has.
SELECT DISTINCT
pt.id,
pt.category_id
FROM
season s
JOIN part pt
ON s.part_id = pt.id
WHERE
s.type = 'Type A'
To further restrict for a specific vendor, add the vendor clause in is easy enough as it is the basis of the of the vendor "v" at the outer criteria, and the vendor reference to the second LEFT-JOIN that also has the vendor alias available to filter out.
From your description, it seems you are looking to count how many parts in each category each vendor could have listed as as product but hasn't.
That's basically the difference between how many parts can be listed for each category, and how many were actually listed.
So you could count the possible and left join to a count of the actual.
Based on the sqlfiddle, the code below also assumes that you want to be able to focus on one season type, and that only parts (with sales?) listed in partdata are relevant.
select c.name as category
, v.name as vendor
, cpartcount.parts
, cpartcount.parts-coalesce(cvpartcount.parts,0) as missingparts
from vendor v
cross join
(
select pt.category_id, count(pt.id) as parts
from part pt
where pt.id in
(
select s.part_id
from season s
where s.type='Type A'
)
and pt.id in
(
select pd.part_id
from part_data pd
)
group by pt.category_id
) cpartcount
join category c
on cpartcount.category_id=c.id
left join
(
select pt.category_id, v.id as vendor_id, count(pt.id) as parts
from part pt,vendor v
where (v.id,pt.id) IN
(
select p.vendor_id, s.part_id
from product p
join inventory i
on p.id=i.product_id
join season s
on i.season_id = s.id
join part_data pd
on s.part_id=pd.part_id
where s.type='Type A'
)
group by pt.category_id,v.id
) as cvpartcount
on cpartcount.category_id=cvpartcount.category_id
and v.id=cvpartcount.vendor_id
The problem is that the 2'nd query has a GROUP BY on a field from the sub-query (vendor) that is join in LEFT JOIN so it will create an output row per each of the vendors (including NULL for rows from season that don't have a match with the sub-query).
More specifically - your count is on
COUNT(CASE WHEN in_stock IS NULL THEN pt.id END) AS missing_parts
(I would prefer writing SUM(in_stock IS NULL))
but since in_stock is an aggregation result per each vendor - you'll never have a NULL value there. (check the sub-query results)
I think you should clarify the goal of your queries. For example - the first one is returning -
Per each category the number of parts it has on the given seasons, and the number of seasons that this category wasn't available (and not the number of missing parts, since there is no join on category with the sub-query).

Many To Many join with additional where

I think I have a somewhat trivial question but I can't figure out how this works. I have the following Companies and Products tables with a simple Many-To-Many relationship.
How would I have to extend this query, so that the results just contains let's say all companies which have products with id 1 AND 2?
I tried adding wheres and havings wherever I could imagine but all i could get was all companies which have products with id x (without the additional and)
Companies Table
id | name
-----------------
1 | Company 1
2 | Company 2
3 | Company 3
Companies_Products Table
id | product_id | company_id
----------------------------
1 | 1 | 1
2 | 2 | 1
3 | 3 | 1
4 | 1 | 2
5 | 1 | 3
6 | 2 | 3
Products Table
id | name
-----------------
1 | Product A
2 | Product B
3 | Product C
Statement
SELECT companies.name,
companies.id AS company_id,
products.id AS product_id
FROM companies
LEFT JOIN company_products
ON companies.id = company_products.company_id
INNER JOIN products
ON company_products.product_id = products.id
If you want ALL companies with associated products 1 and 2, you can write this query:
SELECT c.name,
c.id AS company_id
FROM companies c
WHERE (SELECT COUNT(*)
FROM company_products cp
WHERE cp.company_id = c.id
AND cp.product_id in ('1', '2')
) = 2
Go to Sql Fiddle
If you want to know informations about associated product in the main query so you must use a join in addition of existing query.
Maybe you could using the following subquery in your query:
SELECT company_id, count(*) as no_companies
FROM Companies_Products
WHERE product_id IN (1, 2)
HAVING count(*) = 2
(In this case company an product must be coupled only once.) It returns all the company_ids with product 1 and 2.
There always some discussion about subquery's and performance, but I don't think you will notice.
You could make this function flexible by using a array.
pseudo code:
$parameter = array(1, 2);
...
WHERE product_id IN $parameter
HAVING count(*) = count($parameter)
Please say so if you need more help.

MySQL Join table row based on lowest cell value

I have two tables in a MySQL database like this:
PRODUCT:
product_id | product_name
-----------+-------------
1 | shirt
2 | pants
3 | socks
PRODUCT_SUPPLIER: (id is primary key)
id | supplier_id | product_id | part_no | cost
----+---------------+--------------+-----------+--------
1 | 1 | 1 | s1p1 | 5.00
2 | 1 | 2 | s1p2 | 15.00
3 | 1 | 3 | s1p3 | 25.00
4 | 2 | 1 | s2p1 | 50.00
5 | 2 | 2 | s2p2 | 10.00
6 | 2 | 3 | s2p3 | 5.00
My goal is a query that joins the tables and outputs a single row for each product joined with all fields from the corresponding supplier row with the lowest cost like this:
product_id | product_name | supplier_id | part_no | cost
-----------+---------------+---------------+------------+---------
1 | shirt | 1 | s1p1 | 5.00
2 | pants | 2 | s2p2 | 10.00
3 | socks | 2 | s3p3 | 5.00
At present I do have the following query written which seems to work but I'd like to know from any of the more experienced SQL users if there is a cleaner, more efficient or otherwise better solution? Or if there is anything essentially wrong with the code I have?
SELECT p.product_id, p.product_name, s. supplier_id, s.part_no, s.cost
FROM product p
LEFT JOIN product_supplier s ON
(s.id = (SELECT s2.id
FROM product_supplier s2
WHERE s2.product_id = p.product_id
ORDER BY s2.cost LIMIT 1));
I would run:
select p.product_id, p.product_name, s.supplier_id, s.part_no, s.cost
from product p
join product_supplier s
on p.product_id = s.product_id
join (select product_id, min(cost) as min_cost
from product_supplier
group by product_id) v
on s.product_id = v.product_id
and s.cost = v.min_cost
I don't see the point in an outer join. Is every product is on the product_supplier table? If not then the outer join makes sense (change the join to inline view aliased as v above to a left join if that is the case).
The above may run a little faster than your query because the subquery is not running for each row. Your current subquery is dependent and relative to each row of product.
If you want to eliminate ties and don't care about doing so arbitrarily you can add a random number to the end of the results, put the query into an inline view, and then select the lowest/highest/etc. random number for each group. Here is an example:
select product_id, product_name, supplier_id, part_no, cost, min(rnd)
from (select p.product_id,
p.product_name,
s.supplier_id,
s.part_no,
s.cost,
rand() as rnd
from product p
join product_supplier s
on p.product_id = s.product_id
join (select product_id, min(cost) as min_cost
from product_supplier
group by product_id) v
on s.product_id = v.product_id
and s.cost = v.min_cost) x
group by product_id, product_name, supplier_id, part_no, cost
If for some reason you don't want the random # to come back in output, you can put the whole query above into an inline view, and select all columns but the random # from it.

Duplicated Data When Joining 4 Tables in MySql [duplicate]

This question already has answers here:
Sum total of table with two related tables
(2 answers)
Closed 9 years ago.
I have 4 tables, with the relevant columns summarized here:
customers:
id
name
credits:
id
customer_id # ie customers.id
amount
sales:
id
customer_id # ie customers.id
sales_items:
id
sale_id # ie sales.id
price
discount
The idea is that customers lists all of our customers, credits lists each time they have paid us, sales lists each time they have bought things from us (but not what things they bought) and sales_items lists all of the items they bought at each of those sales. So you can see that credits and sales both relate back to customers, but sales_items only relates back to sales.
As an example dataset, consider:
customers:
id | name
5 | Carter
credits:
id | customer_id | amount
1 | 5 | 100
sales:
id | customer_id
3 | 5
sales_items:
id | sale_id | price | discount
7 | 3 | 5 | 0
8 | 3 | 0 | 0
9 | 3 | 10 | 0
I have tried this in MySQL:
SELECT c.*,
SUM( cr.amount ) AS paid,
SUM( i.price + i.discount ) AS bought
FROM customers AS c
LEFT JOIN sales AS s ON s.customer_id = c.id
LEFT JOIN sales_items AS i ON i.sale_id = s.id
LEFT JOIN credits AS cr ON cr.customer_id = c.id
WHERE c.id = 5
But it returns:
id | name | paid | bought
5 | Carter | 300 | 15
If I omit the SUM() functions, it returns:
id | name | paid | bought
5 | Carter | 100 | 5
5 | Carter | 100 | 0
5 | Carter | 100 | 15
So it looks like it's returning one row for every record matched in sales_items, but it's filling in the amount column with same value from credits each time. I see that this is happening, but I'm not understanding why it's happening.
So, two questions:
1. What is happening that it's smearing that one value through all of the rows?
2. What SQL can I throw at MySQL so that I can get this back:
id | name | paid | bought
5 | Carter | 100 | 15
I know that I could break it all up in subqueries, but is there a away to do it just with joins? I was hoping to learn a thing or two about joins as I tackled this problem. Thank you.
Edit: I created an SQL Fiddle for this: http://sqlfiddle.com/#!2/0051b/1/0
select distinct (c.id, c.name), sum(i.price+i.discount) AS bought, cr.amount AS paid
from customer c, credits cr, sales s, sales_items i
where s.customer_id = c.id
and i.sale_id = s.id
and cr.customer_id = c.id and c.id = 5
group by c.id, c.name;
I'm not very sure, but try this. Use group by; that is surely the solution.
Please try this
SELECT c.*,( SELECT SUM( cr.amount ) FROM customer c INNER JOIN credits cr ON
cr.customer_id = c.id WHERE c.id = 5 GROUP BY cr.id ) AS paid
,SUM( i.price + i.discount ) AS bought
FROM customers AS c INNER JOIN sales s ON s.customer_id = c.id
INNER JOIN sales_items i ON i.sale_id = s.id
INNER JOIN credits cr ON cr.customer_id = c.id
WHERE c.id = 5 GROUP BY s.id,cr.id