SELECT
category_id,
product_size,
category_name,
SUM(product_quantity) AS total_quantity
FROM tbl_categories_quantity -- (table-1)
INNER JOIN tbl_categories USING (category_id)
GROUP BY category_id,product_size
The Above Code is working in a single table, and I want to Add the below code (2nd table) that does not work
SELECT
category_id,
product_size,
SUM(product_sell) AS total_sell
FROM tbl_product_sell -- (table-2)
GROUP BY category_id,product_size;
From first subquery retrieves category and product size wise total quantity and second one retrieves total sales based on category and product size. Then combine this two subquery with LEFT JOIN because sometimes sale may not happen. COALESCE() is used for replacing NULL value to 0 (zero). If specific category or product size wise data are required then use WHERE clause in both the subquery. As category id is unique so MAX(category_name) is used otherwise category name must be placed in GROUP BY clause. Subtract total sale from total quantity for calculating available quantity.
-- MySQL
SELECT t.category_name category
, t.product_size
, t.product_quantity
, COALESCE(p.total_sell, 0) product_sell
, (t.product_quantity - COALESCE(p.total_sell, 0)) available_in_stock
FROM (SELECT tc.category_id
, tcq.product_size
, MAX(tc.category_name) category_name
, SUM(tcq.product_quantity) product_quantity
FROM tbl_categories tc
INNER JOIN tbl_categories_quantity tcq
ON tc.category_id = tcq.category_id
GROUP BY tc.category_id
, tcq.product_size) t
LEFT JOIN (SELECT category_id
, product_size
, SUM(product_sell) total_sell
FROM tbl_stock_sell
GROUP BY category_id
, product_size) p
ON t.category_id = p.category_id
AND t.product_size = p.product_size
Please check from url https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=b8c54aa656d9dc930fcb7a93d2bc0960
N.B.: Table name or column name may vary based on your DB.
Related
I have two tables, let's say OrderPlaced and OrderDelivered.
The OrderPlaced table looks like this:
In a single order we can have multiple products(which is defined by sku in the table) and each product can have multiple quantity.
The OrderDelivered table looks like this:
So technically 3 products have not been delivered. Orderid 1000 - product S101, Orderid 1001 - product S102(as 3 quantity required, but 2 delivered) and Orderid 1002 - product S100.
I am trying to write a SQL query that can give me the OrderId and sku those have not been delivered. For now I have written something like
select OrderPlaced.orderid,OrderPlaced.sku
from OrderPlaced
left join OrderDelivered
on OrderPlaced.Orderid = OrderDelivered.orderid and OrderPlaced.sku = OrderDelivered.sku
where OrderDelivered.sku is NULL;
This is giving me Orderid 1000 - product S101 and Orderid 1002 - product S100, but Orderid 1001 - product S102 is missing. I understand I have to do a check on qty as well, but couldn't think how to do that. I would really appreciate it if someone can help me with that part.
Add up the deliveries per order and sku and then outer join the delivered quantities to the order table so you can compare the quantities.
select
p.orderid,
p.sku,
p.qty as ordered,
coalesce(d.sum_qty, 0) as delivered
from orderplaced p
left join
(
select orderid, sku, sum(qty) as sum_qty
from orderdelivered
group by orderid, sku
) d on d.orderid = p.orderid and d.sku = p.sku
where p.qty > coalesce(d.sum_qty, 0)
order by p.orderid, p.sku;
Your query works for any items that have not been delivered at all, this is your WHERE OrderDelivered.sku IS NULL. But you can also have a scenario in which fewer items are delivered than ordered, and importantly, you can have multiple records related to your deliveries even if they refer to the same order and sku (two rows with 1 qty each).
In this case you will need to sum up all the deliveries per placed order id, sku and quantity (GROUP BY clause in the query below) check if that sum (or 0 if nothing is found) differs from the placed order (HAVING clause). You could use such a query:
SELECT OrderPlaced.orderid, OrderPlaced.sku,
OrderPlaced.qty - COALESCE(SUM(OrderDelivered.qty), 0) AS qty_missing,
CASE
WHEN SUM(OrderDelivered.qty) IS NULL
THEN 'Yes'
ELSE 'No'
END AS is_missing_completely
FROM OrderPlaced
LEFT
JOIN OrderDelivered
ON OrderPlaced.Orderid = OrderDelivered.orderid
AND OrderPlaced.sku = OrderDelivered.sku
GROUP BY OrderPlaced.orderid, OrderPlaced.sku, OrderPlaced.qty
HAVING OrderPlaced.qty != COALESCE(SUM(OrderDelivered.qty), 0)
Here's a live demo on dbfiddle
I would create two aggregated representations of your ordered and delivered products, and then outer join them to get the differences. If you are using MySql 8 you can represent these as a CTE, otherwise just use two equivalent sub-queries
with op as (
select OrderId, Sku, Sum(qty) Qty
from OrderPlaced
group by OrderId, Sku
), od as (
select OrderId, Sku, Sum(qty) Qty
from OrderDelivered
group by OrderId, Sku
)
select op.OrderId, op.Sku, op.Qty - Coalesce(od.qty,0) notDelivered
from op
left join od on od.orderid = op.orderid and od.sku = op.sku
where op.Qty - Coalesce(od.qty,0)>0;
Example DB<>Fiddle
Note: I have simplified the question since both that and the answer have become I believe more complex than intended.
I want to an an anti-join that has a condition other than just not existing in the first table.
Table Product / Manufacturer
Widget / Acme
Paddle / Acme
Ball / Acme
Gas / Exxon
Pump / Exxon
Table: Customer / Product
Karen / Ball
Bob / Paddle
Karen / Gas
Bob / Pump
A "normal" anti-join would find out which products have not been ordered via
Select Products from `Product / Manufacturer` as T1
Left Join `Customer / Product` as T2
On T2.Zip is NULL
However what I am looking for is which customers didn't order which products, in essence:
Select Products from `Product / Manufacturer`
where Manufacturer = 'Acme' that do not exist in `Customer / Product`
where Customer = 'Karen'
and
Select Products from `Product / Manufacturer`
where Manufacturer = 'Exxon' that do not exist in `Customer / Product`
where Customer = 'Karen'
and
Select Products from `Product / Manufacturer`
where Manufacturer = 'Acme' that do not exist in `Customer / Product`
where Customer = 'Bob'
and
Select Products from `Product / Manufacturer`
where Manufacturer = 'Exxon' that do not exist in `Customer / Product`
where Customer = 'Bob'
'
But as one query since there are 100s of "Customers" and 100s of Manufacturers.
If you want to exclude all products for a manufacturer for which no product from that manufacturer appears in any order...
Then that means that you only want to include only products from certain manufacturers...
Which manufacturers have had a product appear in an order ?
SELECT r.manufacturer
FROM products r
JOIN orders s
ON s.product = r.product
GROUP BY r.manufacturer
You can wrap that query in parens and include it as an inline view ...
SELECT p.*
FROM ( SELECT r.manufacturer
FROM product r
JOIN orders s
ON s.product = r.product
GROUP BY r.manufacturer
) q
JOIN product p
ON p.manufacturer = q.manufacturer
LEFT
JOIN orders o
ON o.product = p.Product
WHERE o.product IS NULL
There are other query patterns that will return an equivalent result.
FOLLOWUP
NOTE: The "breakdown by gender/hour" part wasn't made clear in the original specification.
The query pattern is very much the same. Use an inline view query to return a distinct list of manufacturers for each gender/hour.
Then join that set to the product table, to get every product from those manufacturer. That will included products that were ordered, as well as products that weren't ordered.
Then apply the anti-join pattern, to exclude the products that were ordered by gender/hour.
SELECT q.gender
, q.hour
, p.manufacturer
, p.product
FROM ( SELECT s.gender
, s.hour
, r.manufacturer
FROM orders s
JOIN product r
ON r.product = s.product
GROUP
BY s.gender
, s.hour
, r.manufacturer
) q
JOIN product p
ON p.manufacturer = q.manufacturer
LEFT
JOIN orders o
ON o.gender = q.gender
AND o.hour = q.hour
AND o.product = p.product
WHERE o.product IS NULL
If that's not clear, consider that the following query returns an equivalent set. The inline line view query t returns the set of all products from a manufacturer, by gender/hour.
This query is somewhat less efficient (at least in MySQL) due to the additional inline view. And while longer, it may be more understandable, since the view query t makes explicit the set of all possible rows that could be returned... every product by manufacturer/gender/hour. (To see that set, the view query t can be pulled out and run separately to see what it returns.)
In the outermost query, t is referenced as if it were a table. If it t were replaced by a simple table reference, the query would just be a simple anti-join. All rows from t excluding rows that have a match.
SELECT t.gender
, t.hour
, t.manufacturer
, t.product
FROM (
SELECT q.gender
, q.hour
, q.manufacturer
, p.product
FROM ( SELECT s.gender
, s.hour
, r.manufacturer
FROM orders s
JOIN product r
ON r.product = s.product
GROUP
BY s.gender
, s.hour
, r.manufacturer
) q
JOIN product p
ON p.manufacturer = q.manufacturer
) t
LEFT
JOIN orders o
ON o.gender = t.gender
AND o.hour = t.hour
AND o.product = t.product
WHERE o.product IS NULL
I recommend you get the set of rows returned first. Before you futz with adding a GROUP BY and a GROUP_CONCAT aggregate to collapse the rows.
If you want to group multiple values of "hour" into just "am" or "pm", you can use an expression (in place of "hour") that returns "am" or "pm". (Think in terms of that expression being another column in the table; but instead of referencing a column in the table, you use an expression that derives the value from other columns in the table.
IF(x.hour<12,'am','pm')
I have two tables; One contains for products stats and another one contains additional stats
StatsHourly:
id
product_id (can be multiple)
amount
cost
time
StatsValues:
id
product_id (can be multiple)
value (double)
I need to join those two tables and get something like this in the result:
product_id
sum (amount)
sum (cost)
sum (value)
I'm trying to do this:
"SELECT
SUM(s.amount) as amount,
SUM(s.cost) as cost
FROM StatsHourly s
LEFT JOIN (
SELECT
COALESCE(SUM(value), 0) as value
FROM StatsValues
GROUP BY product_id
) value v ON v.product_id = s.product_id
WHERE 1
AND s.product_id = :product_id";
This doesn't work. Could someone show me the right way to do it?
You have an extra comma after as cost:
SUM(s.cost) as cost, <-- here
You also use 2 aliases for the subquery, you should remove value from there:
) value v
You do not use any output from the subquery.
Coalesce() is unnecessary in the subquery.
This works (tested):
SELECT
s.product_id as product_id,
s.amount_s as amount,
s.cost_s as cost,
v.value_v as value
FROM
(SELECT
product_id,
SUM(amount) as amount_s,
SUM(cost) as cost_s
FROM StatsHourly
GROUP BY product_id) as s
LEFT JOIN
(SELECT
product_id,
SUM(value) as value_v
FROM StatsValues
GROUP BY product_id) as v
ON v.product_id = s.product_id;
WHERE s.product_id = 'product_id';
The point is:
As you have multiple equal product_id in BOTH table you have to make two aggregated tables through subqueries that makes the product_id unique and sum all appropriate rows.
After that you can join and you select the already aggregated values.
Regards
I want to get all order id numbers for selected customer which not paid till now, my data show as following:
What I want is Write a SELECT statement that answers this question:
select orderID
from order
where customer id = #custID
and Total cashmovementValue
for current order id
is less than total (sold quantity * salePrice )
for current order id
How to do it?
Thanks.
You need to compare the sum of each order line with the sum of each payment per order. GROUP BY and a few sub-queries is what you need to get the job done.
Something like this should work:
SELECT
O.OrderID
FROM [Order] O
INNER JOIN (
-- Add up cost per order
SELECT
OrderID,
SUM(SoldQuantity * P.SalePrice) AS Total
FROM OrderLine
INNER JOIN Product P ON P.ProductID = OrderLine.ProductID
GROUP BY OrderID
) OL ON OL.OrderID = O.OrderID
LEFT JOIN (
-- Add up total amount paid per order
SELECT
OrderID,
SUM(CashMovementValue) AS Total
FROM CashMovement
GROUP BY OrderID
) C ON C.OrderID = O.OrderID
WHERE
O.CustomerID = #custID
AND ( C.OrderID IS NULL OR C.Total < OL.Total )
EDIT
I've just noticed you're not storing the sale price on each order line. I've updated my answer accordingly, but this is a very bad idea. What will happen to your old orders if the price of an item changes? It is okay (and actually best practice) to denormalise the data by storing the price at the time of sale on each order line.
I have two tables (listed only fields important for the question):
t_groups
INT groupId PRIMARY
VARCHAR(255) grname
t_goods
INT goodId PRIMARY
INT groupId
INT price
VARCHAR(255) name
Now I need a query, which selects group names and name of the cheapest good in each group. Tried doing it this way:
SELECT gr.groupId, grname, g.name
FROM t_groups AS gr
LEFT JOIN (SELECT * FROM t_goods ORDER BY PRICE ASC LIMIT 1) AS g
ON g.groupId = gr.groupId
but it doesn't work — returns NULLs in g.name field. It could be easily explained:
SELECT within JOIN statement selects cheapest good first, and then tries to "filter it" by groupId. Obviously, it'll only work for the group cheapest good belongs to.
How do I solve the task?
Why your query does not work
SELECT gr.groupId, grname, g.name
FROM t_groups AS gr
LEFT JOIN (SELECT * FROM t_goods ORDER BY PRICE ASC LIMIT 1) AS g
ON g.groupId = gr.groupId
The inner query selects the absolutely cheapest good (irrespective of group) in your database. Therefore, when you LEFT JOIN the groups to this result set, only the group which actually includes the universally cheapest good has a matching row (that group should get the g.name column filled properly). However, due to the way LEFT JOIN works all other groups will get NULL as the value of all columns in g.
The correct solution
First, you need to select the cheapest price in each group. This is easy:
SELECT groupId, MIN(price) AS minPrice FROM t_goods GROUP BY (groupId)
However the cheapest price is not useful without the associated goodId. The problem is that it's not meaningful to write something like:
/* does not make sense, although MySql has historically allowed it */
SELECT goodId, groupId, MIN(price) AS minPrice FROM t_goods GROUP BY (groupId)
The reason is that you cannot select a non-grouped column (i.e. goodId) unless you wrap it in an aggregate function (such as MIN): we don't know which goodId you want from among those that share the same groupId.
The correct, portable way to get the goodId of the cheapest goods in each group is
SELECT goodId, temp.groupId, temp.minPrice
FROM (SELECT groupId, MIN(price) AS minPrice FROM t_goods GROUP BY groupId) temp
JOIN t_goods ON temp.groupId = t_goods.groupId AND temp.minPrice = t_goods.price)
The above query first finds out the cheapest price per group, and then joins to the goods table again to find the goodIds of the goods having that price inside that group.
Important: if multiple goods have an equal cheapest price in a group, this query will return all of them. If you only want one result per group you have to specify the tiebreaker, for example:
SELECT MIN(goodId), temp.groupId, MIN(temp.minPrice)
FROM (SELECT groupId, MIN(price) AS minPrice FROM t_goods GROUP BY groupId) temp
JOIN t_goods ON temp.groupId = t_goods.groupId AND temp.minPrice = t_goods.price)
GROUP BY temp.groupId
With this query in hand, you can then find the name and price of the single cheapest good in each group (lowest goodId will be used as tiebreaker):
SELECT groupId, grname, gd.name, t3.minPrice
FROM t_groups AS gr
LEFT JOIN (SELECT MIN(goodId) AS goodId, t1.groupId, MIN(t1.minPrice) AS minPrice
FROM (SELECT groupId, MIN(price) AS minPrice FROM t_goods GROUP BY groupId) t1
JOIN t_goods ON t1.groupId = t_goods.groupId AND t1.minPrice = t_goods.price
) t2
) t3 ON gr.groupId = t3.groupId
LEFT JOIN t_goods gd ON t3.goodId = gd.goodId
This final query performs two joins at its "outer" level:
joins groups with the "goodId and cheapest price for each group" table to get the goodId and cheapest price
then joins with the goods table to get the name of the good with this goodId
It will produce only one good per group, even if multiple goods are tied for cheapest.
Here's how you could do it:
select
t_groups.grname as `name of group`,
t_goods.name as `name of good`
from (
select
groupId,
min(price) as min_price
from t_goods
group by groupId
) as mins
inner join t_goods
on mins.groupId = t_goods.groupId and mins.min_price = t_goods.price
inner join t_groups
on mins.groupId = t_groups.groupId
How this works:
mins subquery gets the minimum price for each groupId
joining mins to t_goods pulls all of the goods out that have the minimum price in their group. Note that this could return multiple goods in a single group, if there are multiple goods with the minimum price
that's then joined to t_groups to get the group name
Your query was presumably returning NULLs because it was left joining to a subquery with only one row.