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.
Related
I am trying to create an SQL query that conditionally pulls data from multiple tables.
I have four tables:
orders
+------+------------+------------+
| id | date_added | currency |
+------+------------+------------+
| 1 | 2018-07-23 | 1 |
+------+------------+------------+
order_items
+------+------------+------------+---------------+---------------+
| id | order_id | price | product_id | product_type |
+------+------------+------------+---------------+---------------+
| 1 | 1 | 100.00 | 1 | ticket |
+------+------------+------------+---------------+---------------+
order_data
+------+--------------+---------------+
| id | order_id | ext_order_ref |
+------+--------------+---------------+
| 1 | 1 | ABC |
+------+--------------+---------------+
products
+------+------------+------------+
| id | date | product_id |
+------+------------+------------+
| 1 | 2020-03-12 | 1 |
+------+------------+------------+
| 2 | 2020-03-18 | 2 |
+------+------------+------------+
| 3 | 2020-03-20 | 3 |
+------+------------+------------+
I need to output orders with the following conditions:
Each order in a row with total (calculated from order items with matching order id)
The 'ext_order_ref' from the order_data table that matches that order
Only include order items that have a specific product type
Only include orders with products from a particular date range
Preferred output would look like this:
+------------+------------+--------------+
| order_id | total | ext_order_ref|
+------------+------------+--------------+
| 1 | 100 | ABC |
+------------+------------+--------------+
My current query is basically like this; please advise
SELECT
orders.id as order_id,
SUM(order_items.price) as total,
order_data.ext_order_ref
FROM orders
INNER JOIN order_data
ON orders.id = order_data.ext_order_ref
RIGHT JOIN order_items
ON orders.id = order_items.order_id
LEFT JOIN products
ON order_items.product_id = products.product_id
WHERE order_items.product_type = 'ticket' AND products.date BETWEEN '2020-03-12' AND '2020-03-18'
GROUP BY orders.id
It almost works, but not quite. The date particularly is causing issue.
Thanks in advance!
First decide the driving table for the query and form the query based on the driving table.
Driving table is the one primary table from which other tables join.
More information on driving tables from askTom
In your case, the driving table is Orders table. You are switching between RIGHT OUTER JOIN and LEFT OUTER JOIN. This will cause confusion in the resultset.
I have modified the query. See whether it works.
SELECT
orders.id as order_id,
SUM(order_items.price) as total,
order_data.ext_order_id
FROM orders
INNER JOIN order_data
ON orders.id = order_data.ext_order_id
LEFT OUTER JOIN order_items
ON orders.id = order_items.order_id
LEFT OUTER JOIN products
ON order_items.product_id = products.product_id
WHERE order_items.product_type = 'ticket' AND products.date BETWEEN '2020-03-12' AND '2020-03-18'
GROUP BY orders.id
I don't understand why you would want outer joins at all. If I follow the conditions correctly:
SELECT o.id as order_id,
SUM(oi.price) as total,
od.ext_order_id
FROM orders o INNER JOIN
order_data od
ON o.id = od.ext_order_id INNER JOIN
order_items oi
ON o.id = oi.order_id INNER JOIN
products p
ON oi.product_id = p.product_id
WHERE oi.product_type = 'ticket' AND
p.date >= '2020-03-12' AND
p.date < '2020-03-19'
GROUP BY o.id, od.ext_order_id;
Note the use of table aliases so the query is easier to write and read.
I've made a little database in SQL that as 2 tables Product (Name, Ingredient and Available (Ingredient):
| Product | Available |
| Name | Ingredient | Ingredient |
| 1 | a | a |
| 1 | b | c |
| 2 | a |
| 2 | c |
I want the name of a product only if ALL its ingredients are inside the Available table.
For the previous example, the result should be: Product "2"
and not Product "1", because I don't have the ingredient "b" in the Available table.
Thanks for the help
You can try with left join (to figure out which Products don't have necessary Ingredients) and group by + having to filter Products that have at least one missing Ingredient:
select p.Name
from Products p
left join Available a on a.Ingredient = p.Ingredient
group by p.Name
having sum(a.Ingredient is null) = 0
You can try something like this also:
WITH TEMP_PRODUCTS AS
(
SELECT NAME, COUNT(1) AS NUMBER_OF_INGREDIENTS
FROM PRODUCT
GROUP BY PRODUCT
)
SELECT PRD.NAME, COUNT(1) AS NUMBER_OF_AVAILABLE_INGREDIENTS
FROM PRODUCT PRD
JOIN TEMP_PRODUCTS TMP ON PRD.NAME = TMP.NAME
WHERE EXISTS (SELECT 1
FROM INGREDIENT ING
WHERE ING.INGREDIENT = PRD.INGREDIENT)
GROUP BY PRD.NAME
HAVING COUNT(1) = TMP.NUMBER_OF_INGREDIENTS;
I have a MySQL DB with the following Tables:
Products:
Product_ID | Product_Name
1 | Blaster
2 | Faser
3 | BFG
Orders:
Order_ID | Product_ID | Order_Product_Qnt
1 | 1 | 10
2 | 2 | 5
3 | 3 | 7
4 | 2 | 10
Sells:
Sell_ID | Product_ID | Sel_Product_Qnt
1 | 2 | 5
2 | 1 | 1
3 | 3 | 2
What I want to do is a query that lists all the products followed by their amount.
The result should be:
Product_Name | Quantity
BFG | 5
Blaster | 9
Faser | 10
Following Barnar's suggestion I got to this piece of code:
SELECT
Products.Product_Name,
COALESCE (SUM(Orders.Order_Product_Qnt), 0) - COALESCE (SUM(Sells.Sells_Product_Qnt), 0) AS Quantity
FROM
Products
LEFT JOIN
Orders ON Products.Product_ID = Orders.Product_ID
LEFT JOIN
Sells ON Products.Product_ID = Sells.Product_ID
GROUP BY
Products.Product_Name
The query works but it returns wrong values.
For example, I have a product that has 6 orders, and 1 sell, logic dictates that 6-1=5, but that query gives me 4 as a result.
Or another one with 18 Orders and 6 Sells, returns 60 (should be 12).
Any advise is appreciated.
Maybe something like this?
SELECT
product_name,
orders_cnt - sales_cnt AS Quantity
FROM (
SELECT product_name,
SUM(orders) AS orders_cnt,
SUM(sales) AS sales_cnt
FROM (
SELECT products.product_name,
ifnull(orders.order_product_qnt, 0) orders,
ifnull(sells.sells_product_qnt,0) sales
FROM products
LEFT JOIN orders ON products.product_id = orders.product_id
LEFT JOIN sells ON products.product_id = sells.product_id
) t1
GROUP BY product_name ) t2
Finally got it working, forgot to post my solution here:
SELECT
Products.Product_ID,
Products.Product_Name,
IFNULL(b.SB - c.SC, 0) AS Quantity,
FROM Produtos_Table
LEFT JOIN (
SELECT
Product_ID,
SUM(Quantity) AS SB
FROM
Orders
GROUP BY Product_ID
) b
ON Products.Product_ID = b.Product_ID
LEFT JOIN (
SELECT
Product_ID,
SUM(Sell_Product_Qnt) AS SC
FROM
Sells
GROUP BY
Product_ID
) c
ON Products.Product_ID = c.Product_ID
GROUP BY
Products.Product_Name
i am thinking if there is a better way to query product, product options and product option choices from database
my database structure is like:
product table (product_id, product_name)
product_options table (option_id, option_name, product_id)
product_optionChoices (Choice_id, choice_name, Option_id)
i want a single query that return all product and options in seperate rows like:
--------------------------------------------------------------------------------
| product_id | product_name | option_id | option_name | choiceId | choice_name |
| 1 | shoes | | | | |
| 1 | | 1 | choose color| | |
| | | 1 | | 1 | red |
| | | 1 | | 2 | blue |
--------------------------------------------------------------------------------
right now i am using 3 sepearte queries to get product, product option and product_optionChoices and appending the query together using QueryAddRow in coldfusion.
Thanks
Maybe this solution helps you even if i don't like it.
This returns exactly what you want (UPDATED).
SELECT p.product_id, p.product_name, '' as option_id, '' as option_name, '' as choiceId, '' as choice_name FROM `product` p
UNION ALL
SELECT pr.product_id as product_id, '' as product_name, o.option_id, o.option_name, '' as choiceId, '' as choice_name FROM `product_options` o
INNER JOIN product pr
ON pr.product_id = o.product_id
UNION ALL
SELECT '' as product_id, '' as product_name, op.option_id as option_id, '' as option_name, c.choice_Id, c.choice_name FROM `product_optionChoices` c
INNER JOIN product_options op
ON op.option_id = c.option_id
This works for SQL Server
SELECT P.product_id, P.product_name, PO.option_id, PO.option_name, POC.choiceId, POC.choice_name
FROM product P LEFT OUTER JOIN product_options PO on P.product_id = PO.product_id
LEFT OUTER JOIN product_optionChoices POC on POC.option_id = PO.Option_id
I would like to get the following results from my query:
id_product_attribute | id_product | reference | name | total
12 | 1 | 234235 | product_name | 2
14 | 2 | 235435 | product_name | 7
16 | 3 | 235325 | product_name | 4
etc
but when I use this query:
select pa.id_product_attribute, p.id_product, pa.reference, cl.name, sum(od.product_quantity) as total
from ps_product_attribute pa
left join ps_order_detail od on od.product_attribute_id = pa.id_product_attribute
left join ps_product p on pa.id_product = p.id_product
left join ps_category_product cp on cp.id_product = p.id_product
left join ps_category_lang cl on cp.id_category = cl.id_category
where cp.id_category = 141 and cl.id_lang = 6;
it gives me only this results:
id_product_attribute | id_product | reference | name | total
12 | 1 | 234235 | product_name | 13
so in the 'total' column it shows me the total of all, instead of seperate per row.
can anyone tell me what I'm doing wrong in my query?
Your source data would help, but at the very least you forgot to GROUP BY at the end. With the current query, you should add;
GROUP BY pa.id_product_attribute, p.id_product, pa.reference, cl.name
With MySQL, you can choose to GROUP BY less columns and get a random selection of values in the other ones, but if possible, you should GROUP BY all columns that don't have aggregates (like SUM in this case) on them.
Aggregate functions should be used with GROUP BY CLAUSE