SQL query involving comparison of sets - mysql

Background
Products can be sold as bundles. Following tables are present: products, bundles, bundles_products, orders, orders_products.
An order would be said to "contain" a bundle if it contains all the bundle's products.
Problem
How would one go about counting orders for bundles?
Example
products table
id name
1 broom
2 mug
3 spoon
4 candle
bundles table
id name
1 dining
2 witchcraft
bundles_products table
bundle_id product_id
1 2
1 3
2 1
2 4
orders_products table
order_id product_id
1000 1
1000 3
1001 1
1001 2
1001 3
The query would return the following table:
bundle orders
dining 1
witchcraft 0
Notes
The example intentionally misses the orders table as it is not relevant what it contains.
Of course, this could be approached imperatively, by writing some code and gathering the data, but I was hoping there is a declarative, SQL way of querying for this kind of things?
One idea I had was to use a GROUP_CONCAT to concatenate all the products of a bundle and somehow compare that with products of each order. Still, a long way from clear.

One way is to use two Derived Tables (subqueries). In first subquery, we will fetch the total number of unique products for every bundle. In the second subquery, we will fetch the total products in an order, for a combination of order and bundle.
We will LEFT JOIN them on bundle_id as well as matching the total count of products per bundle in them. Eventually, we will do a grouping on bundle, and count the number of orders matching successfully.
SELECT dt1.id AS bundle_id,
dt1.name AS bundle,
Count(dt2.order_id) AS orders
FROM (SELECT b.id,
b.name,
Count(DISTINCT bp.product_id) AS total_bundle_products
FROM bundles AS b
JOIN bundles_products AS bp
ON bp.bundle_id = b.id
GROUP BY b.id,
b.name) AS dt1
LEFT JOIN (SELECT op.order_id,
bp.bundle_id,
Count(DISTINCT op.product_id) AS order_bundle_products
FROM orders_products AS op
JOIN bundles_products AS bp
ON bp.product_id = op.product_id
GROUP BY bp.bundle_id,
op.order_id) AS dt2
ON dt2.bundle_id = dt1.id
AND dt2.order_bundle_products = dt1.total_bundle_products
GROUP BY dt1.id,
dt1.name
SQL Fiddle DEMO

Here's the brief example, which lacks some parts, I omitted because I don't know precise database structure. Logic is such:
Temp table is generated, which consists of 3 rows - order, count of
products related to bundle, count of products in bundle
Then we select only orders from this table in which we have those last two
variables equal
select count(order_id) from orders
left join(
select count(*) from bundles_products as bundle_amount,
sum(case when orders_products in (
select names from bundles_products where bundle_id='1') then 1 else 0) as order_total,
orders.order_id
left join product on bundle_products.product_id = products.product_id
left join orders on products.product_id = orders_products.product_id
where bundle_products.bundle_id ='1'
) as my_table
on orders.order_name = my_table.orders
where my_table.bundle_amount = my_table.order_total
Edit: I posted this as a response to previous version of the question, without detailed explanation.
Edit2: fixed query a bit. It can be starting point. Logic is still the same, you can get amount of orders for each bundle_id using it

Related

SQL query to get results between 2 tables, and the second one has 3 possibilities of returning data

Even though my question was warned as similar title, I couldn't find here any similar problem. Let me explain in details:
I've got two tables (I'm working with MySQL) with these values inserted:
table products:
id name
1 TV
2 RADIO
3 COMPUTER
table sales (product_id is A FK which references products(id)):
id quantity product_id
1 50 2
2 100 3
3 200 3
The tv's haven't been sold, radios got 1 sale (of 50 unities) and computers got two sales (one of 100 e other of 200 unities);
Now I must create a query where I can show the products and its sales, but there are some conditions that make that task difficult:
1 - If there's no sales, show obviously NULL;
2 - If there's 1 sale, show that sale;
3 - If there's more than 1 sale, show the latest sale (which I've tried to use function MAX(id) to make it simple, and yet didn't worked);
In the tables example above, I expect to show this, after a proper SQL Query:
products.NAME sales.QUANTITY
TV NULL
RADIO 50
COMPUTER 200
I've been trying lots of joins, inner joins, etc., but couldn't find the result I expect. Which SQL query can give the answer I expect?
Any help will be very appreciated.
Thanks.
Hope the below query works.
SELECT products.name, sl.quantity
FROM products LEFT JOIN (
SELECT product_id, max(quantity) as quantity FROM sales GROUP BY product_id) sl
ON products.id = sl.product_id
In MySQL 8.0 you can do:
with m (product_id, max_id) as ( -- This is a CTE
select product_id, max(id) from sales group by product_id
)
select
p.name,
s.quantity
from products p
left join m on m.product_id = p.id
left join sales s on s.id = m.max_id
If you have an older MySQL, you can use a Table Expression:
select
p.name,
s.quantity
from products p
left join ( -- This is a table expression
select product_id, max(id) as max_id from sales group by product_id
) m on m.product_id = p.id
left join sales s on s.id = m.max_id

MySQL Query - Get Customers who have not bought all parts of a single product ID

Okay so I have 2 tables. One table for Product List and one table for Orders. There will be several of the same ProductID in my Table1 since each ProductID has several parts to it (IE: Part 1 of 7.)
The PartNumber will be a number. How do I design my query to find me all the customers who have purchased one of the part numbers, but not all the part numbers for a single product ID?
I'm just learning the basics of MySQL so any help would be much appreciated!
Table1 - Product List
UniqueIDKey
Product ID
PartNumber
Table2 - Orders
UniqueIDKey
Product ID Ordered
PartNumber Ordered
Customer ID
So an order might look like this:
UniqueIDKey: 77
Product ID Ordered: 1001
PartNumber Ordered: 3
Customer ID: 2000001
And, several rows of my Table1 - Product List might look like this:
UniqueIDKey Product ID PartNumber
77 1001 1
78 1001 2
79 1001 3
You need to know the total number of parts under each product prior
to knowing which customers bought some parts of a product but not the
whole.
The query enclosed by table alias B provides count of parts for
each product.
The query enclosed by table alias A provides for each
<customer,product> pair the total number of bought parts.
Now the rest is to match whether the total number of bought parts is
less than the total number of parts of a product.
In this approach the query would look like below:
SELECT
A.customer_id,
A.product_id,
A.total_parts_of_product_customer_purchased AS total_purchased,
B.total_parts,
B.total_parts - A.total_parts_of_product_customer_purchased AS didnot_purchase
FROM (
SELECT
customer_id,
product_id,
count(part_number) AS total_parts_of_product_customer_purchased
FROM Orders AS ordr
GROUP BY
customer_id, product_id
) AS A
INNER JOIN (
SELECT
product_id,
count(part_number) AS total_parts
FROM product_list AS pl
GROUP BY product_id
) AS B
ON A.product_id = B.product_id
WHERE A.total_parts_of_product_customer_purchased < B.total_parts
ORDER BY A.customer_id;
Use a cross join to get all combinations of customers,product_id's and part_numbers. left join orders table on to this result to get customers who haven't ordered all the parts in a product.
select c.customer_id,p.product_id
from (select product_id,part_number from product_list) p
cross join (select distinct customer_id from orders) c
left join orders o on p.product_id=o.product_id and p.part_number=o.part_number and c.customer_id=o.customer_id
group by c.customer_id,p.product_id
having count(o.part_number) < count(p.part_number)

mySQL SQL joined table then find rows with duplicates

I have two tables:
Orders
orders_ID
orderDate
Order Details
product_ID
order_ID
quantity
Products
product_ID
productName
productDescription
I want to find all the records in the Order Details of product_ID 1 and 4 (so trying to see when one order contains both of these products). So ran this code - the INNER JOIN creates a results table that contains only orders that contain product 1, 4, or 1 and 4.
Then I want to count all of the "Order Details".order_ID duplicates - these would be all of the orders that contain 1 and 4 (note- trivial database - a given order doesn't contain more than 1 of any product).
Here is my code - doesn't quite work - any thoughts?
SELECT order_ID, COUNT(*) TotalCount
FROM
(SELECT * FROM Orders o INNER JOIN "Order Details" od ON o.order_ID = od.order_ID
WHERE od.product_ID = 1 OR od.product_ID = 4)
GROUP BY order_ID
HAVING COUNT(*) > 1
ORDER BY COUNT(*) DESC
Thanks- I looked through the forum but didn't see anything that helped me - been trying for some time.
I think what you want is a JOIN:
select prod1.order_id from `Order Details` prod1 JOIN `Order Details` prod4 on prod1.order_ID=prod2.order_ID where prod1.product_ID=1 and prod2.product_ID=4;

Getting a COUNT() from a GROUP BY

I've got an incredibly convoluted SQL query (three INNER JOINS) that results in an easy to read result set as follows (simplified). I have inherited the db, so it's impossible to change the structure of any of the existing tables, and therefore I have to perform the convoluted query to get to this point:
product_category | product_code
------------------------------------
Hardware 102
Hardware 104
Hardware 102
Software 205
Software 104
If I then simply do a GROUP BY product_category, product_code, I get most of the final result set I'm interested in:
product_category | product_code
------------------------------------
Hardware 102
Hardware 104
Software 205
Software 104
However, what's missing is number in stock:
product_category | product_code | num_in_stock
--------------------------------------------------------
Hardware 102 2
Hardware 104 1
Software 205 1
Software 104 1
Since I want to be able to COUNT() directly from the processing done by the GROUP BY statement, I'm a little lost.
Here is the SQL query thus far:
SELECT categories.product_category, codes.product_code FROM stock
INNER JOIN products ON stock.product_id = products.id
INNER JOIN codes ON products.code_id = codes.id
INNER JOIN categories ON codes.category_id = categories.id
GROUP BY categories.product_category, codes.product_code
The tables are as follows:
CATEGORIES - e.g., "Hardware", "Software"
CODES - e.g., 100, 204 (belongs to a category)
PRODUCTS - combinations of categories + codes, with a useless version #
STOCK - entries of products, if more than one is in stock, there are multiple entries
So the reason this is so messy is because of the useless version # field in PRODUCTS. What this means is that for a particular combo (e.g., "Hardware 102") it can be entered in PRODUCTS multiple times, each with different version # values, which will then cause STOCK to refer to different ids from PRODUCTS, even though, to me, it's the same product. Ugh!
Any ideas?
Edit:
So let's say there's a product "Misc 999" that has two different versions. This means that there will an entry in CATEGORIES of "Misc", in CODES of "999" (with a category_id of that belonging to "Misc"), and two entries in PRODUCTS (both with the same code_id but with different version info - which I'm ignoring).
Then, if we have 10 of these in stock (3 of one version and 7 of the other, but I'm ignoring version info) there will be 10 entries in the STOCK table, each of which will refer to the PRODUCTS table through an id (two different ids, in this case).
Just add count(*) to your select clause:
SELECT categories.product_category, codes.product_code, count(*) qty_in_stock
FROM stock
INNER JOIN products ON stock.product_id = products.id
INNER JOIN codes ON products.code_id = codes.id
INNER JOIN categories ON codes.category_id = categories.id
GROUP BY categories.product_category, codes.product_code
SQLFiddle here.
It's not entirely clear what you want, but perhaps this works:
SELECT categories.product_category
, codes.product_code
, SUM(num_in_stock) as num_in_stock
FROM (
SELECT product_id
, count(*) as num_in_stock
FROM stock
group by product_id
) a
INNER JOIN products
ON a.product_id = products.id
INNER JOIN codes
ON products.code_id = codes.id
INNER JOIN categories
ON codes.category_id = categories.id
GROUP BY categories.product_category
, codes.product_code

Inverse of Inner join (Intersect) with multiple foreign keys

Hi I want to get opposite of intersect from two tables.
I have a sale table and purchase table. What I want to do is get all purchases ids where not included in the sales table.
sale table
sale_id (pk)
product_id (fk)
purchase_id (fk)
purchase table
product_id (fk)
purchase_id (pk)
SELECT DISTINCT purchase_id
, product_id
FROM
purchase
INNER JOIN sale
USING (purchase_id, product_id);
Here is an example:
If I run the above code, this will be the result.
purchase_id product id
1 1
1 2
1 4
2 1
2 3
Now I want to get:
purchase_id product id
1 3
2 2
In short I want to get inverse of above code. Thanks in advance.
Okay, I think I understand better now.
This should return any entry in purchase that have no matching entry in sales.
SELECT
`purchase`.`purchase_id`, `purchase`.`product_id`
FROM `purchase`
LEFT JOIN `sale` ON `sale`.`purchase_id` = `purchase`.`purchase_id` AND `sale`.`product_id` = `purchase`.`product_id`
WHERE
`sale`.`sale_id` IS NULL
ORDER BY
`purchase`.`purchase_id`, `purchase`.`product_id`
If you want to get all the purchases that have no related values in the sales table, you can use a LEFT JOIN:
select
p.purchase_id
from
purchase as p
left join sale as s on p.purchase_id = s.purchase_id
where
s.purchase_id is null;
"Unilateral" joins (LEFT JOIN, RIGHT JOIN) are useful when you want to get data from a table even if data in another related table does not exist. Of course, that means that you can filter data from one table when there's no related data in a second table.
Hope this helps.
Looking at your updated question and your comment, I think that you want all the possible combinations not used.
You'll need to split this in two steps:
First you need all the possible combinations of purchase_id and sale_id values (the "cartesian product" of both the sets).
Then you need to get all the combinations already used.
Finally you need to exclude all the combinations already used.
This can be done using subqueries.
Step 1.
select distinct p.purchase_id, s.product_id from purchase as p, sale as s;
Step 2. (Your query)
select distinct
purchase_id, product_id
from
purchase as p
inner join sale as s
on (p.purchase_id = s.purchase_id and p.product_id = s.product_id);
Step 3. Put it all together
select
a.*
from
(select distinct p.purchase_id, s.product_id from purchase as p, sale as s) as a
left join (
select distinct
purchase_id, product_id
from
purchase as p
inner join sale as s
on (p.purchase_id = s.purchase_id and p.product_id = s.product_id)
) as e on (a.purchase_id = e.purchase_id and a.product_id = e.product_id)
where
e.purchase_id is null and e.product_id is null;