Calculate a ratio from two MySQL SELECT statements - mysql

I have a simple inventory table listing unique items, each record having one of a set of "Item_code". I would like to calculate the percentage of items sold in each Item_code group.
Easy enough to do in 3 steps in PHP, but I cannot figure out how to do it in one statement. Is it possible?
(edited to add the sum(Price) portion.)
1)
Select Item_code, count(*), sum(Price) as value1
From Inventory
group
by Item_Code
2)
Select Item_Code, count(*) , sum(Price) as value2
from Inventory
WHERE Status like "Sold"
group
by Item_code
3) Percent sold = (2) / (1), for each Item_code
Percent value sold = Value2 / Value1
The result should be of the form:
Item Code / Number Listed / Number sold / Percentage / Value listed / Value Sold / Percent Value Sold

count ignores nulls. Thus, you can use a case expression to count the number of items sold without having to use a where clause, and from there on getting the percentage is easy:
SELECT item_code,
COUNT(*) AS listed,
COUNT(CASE status WHEN 'Sold' THEN 1 END) AS sold,
COUNT(CASE status WHEN 'Sold' THEN 1 END) * 100.0 / COUNT(*) AS percentage
FROM inventory
GROUP BY item_code

Related

Get sum of values for multiple categories for a given list of dates

I need to write a query to get the sum of values for each category for a list of given dates. If a value doesn't exist for a category, we should get the value from the previous date. Basically something like "max per category per date". The end goal is a trend chart. If a previous value for a category doesn't exist, setting the value to 0 is fine.
See tables and result below:
Category
id
name
1
savings
2
cash
3
stocks
Item
id
categoryId
value
createdAt
1
1
100
2022-01-01
2
2
20
2022-01-01
3
3
500
2022-01-01
4
2
0
2022-01-02
5
3
1000
2022-01-03
Result
createdAt
total
2022-01-01
620
2022-02-02
600
2022-02-03
1100
To get a result for a single date I could do something like this:
SELECT SUM(value) as total
FROM Category
LEFT JOIN (
SELECT id, categoryId, value
FROM Item
WHERE id IN (
SELECT MAX(id) FROM Item WHERE createdAt <= '2022-01-10' GROUP BY categoryId)
) items ON Category.id = items.categoryId;
I have absolutely no clue on how to approach doing this for multiple dates, eg. if my input would be every day in the month of January 2022. I'm running on MySQL 8.0.23. Also, if this is not feasible with a single query, I'm up for ideas. Do you have any suggestions?
Try this:
with u as
(select id as categoryId from Category),
v as
(select distinct createdAt from Item),
w as
(select * from u cross join v),
x as
(select createdAt,
categoryId,
(select value
from Item
where categoryId = w.categoryId and createdAt <= w.createdAt
order by createdAt desc
limit 1) as value
from w)
select createdAt, sum(value) as total
from x
group by createdAt
Basically getting all the combinations of the creation dates with the categoryIds, then using a subquery to get the value of the closest or equal date for each categoryId.
A Fiddle.
One option uses window functions such as SUM() OVER () and LAG() such as
WITH i AS
(
SELECT SUM(`value`) OVER (PARTITION BY `createdAt`,`categoryId` ORDER BY `createdAt`) AS total_sofar,
LAG(`value`,1,0) OVER (PARTITION BY `categoryId` ORDER BY `createdAt`) AS lg,
`createdAt`
FROM Item
)
SELECT DISTINCT `createdAt`,
SUM(total_sofar) OVER (ORDER BY `createdAt`)-SUM(lg) OVER (ORDER BY `createdAt`) AS total
FROM i
ORDER BY `createdAt`
as you have MySQL DBMS of version 8.0. The trick is grouping(partitioning by categoryId along with the LAG at the first query)
Demo

How to calculate quantity on hand for every lot (mysql)

This is the transaction details table
I am trying to design a Mysql inventory database.
I consider every type: 1 row a product lot (batch).
The type column has 1 for IN and 0 for OUT for each transaction.
detail_id is referencing the id column.
How can I get this result:
id item sum(quantity)
1 1 3 [10-(5+2)]
4 1 0 (5-5)
6 2 20 20
WITH cte AS (
SELECT *,
SUM(detail_id IS NULL) OVER (PARTITION BY item ORDER BY id) group_num
FROM details
)
SELECT MIN(id) id,
item,
SUM( CASE type WHEN 1 THEN quantity
WHEN 0 THEN -quantity
END ) `sum(quantity)`
FROM cte
GROUP BY item, group_num;
fiddle
You can use this:
SELECT
lots.id,
MIN(lots.item) AS item,
MIN(lots.quantity) - IFNULL(SUM(details.quantity), 0) AS quantity
FROM (
SELECT id, item, quantity
FROM details
WHERE type = 1
) lots LEFT JOIN details ON lots.id = details.detail_id
GROUP BY lots.id
ORDER BY lots.id
demo on dbfiddle.uk
I have modified your fiddle. I did change one thing to your existing table - you need to specify detail_id, wherever it is null, so that we can group the result on that.
Final query will look like
select detail_id, item,
(in_sum - out_sum) as `sum(quantity)` from
(SELECT
detail_id,
item,
sum(case when type=1 then quantity else 0 end) as in_sum,
sum(case when type=0 then quantity else 0 end) as out_sum
FROM details
group by detail_id, item) tab
Firstly getting the sum of quantity for specified type by grouping over detail_id, item and then use that result to compute the final output.

Calculating the sum of quantity * unit price in mysql

I think I'm missing a simple step here but I can't seem to figure it out. I've read the other threads and they talk about grouping but I can't seem to put it all together right.
I have a simple table that holds inventory transactions. In each row, there is a quantity and a price. I want to get the sum of the quantity and the sum of the each price * each quantity.
Here's my query. If I remove the grouping, I get 1 result that is multiplied by the number of rows in the table. If I add the grouping, I get the correct result multiple times. Am I missing something here? I just feel like running a query to get 20k results when they all contain the same data would be pointless.
SELECT (SUM(i.quantity) - IFNULL(SUM(s.quantity), 0)) AS quantity,
SUM(i.unitprice * i.quantity) AS totalprice
FROM 02_01_transactions t
LEFT JOIN 02_01_transactions i
ON i.type = 1
AND i.active = 1
LEFT JOIN 02_01_transactions s
ON s.type = 2
AND s.active =1
GROUP BY t.id
Not sure there is a need for the joins (you are not joining on any common value) or the type = 2 rows if you are just subtracting them out. Is there a reason the following does not work?
-- Total quantity, total price of all type 1, active transactions.
SELECT SUM(quantity) AS quantity,
SUM(unitprice * quantity) AS totalprice
FROM 02_01_transactions
WHERE type = 1
AND active = 1
Here's my guess at what you were trying to accomplish:
select
sum(quantity * case type when 1 then 1 when 2 then -1 end) as quantity,
sum(unitprice * quantity) as totalprice
from 02_01_transactions
where type in (1, 2) and active = 1

Mysql SUM of SUM

Is it possible to make a sum of sum in one select query?
Something like this:
SELECT id, SUM(current_price - bought_price)*amount AS profit FROM purchase WHERE purchase_id = 1 GROUP BY id
I want the sum of all returned profit
This is what you are asking:
SELECT tmp.id, SUM(tmp.profit)
FROM (
SELECT id, SUM(current_price - bought_price)*amount AS profit
FROM purchase
WHERE purchase_id = 1
GROUP BY id
) AS tmp
but the result is the same as
SELECT SUM(current_price - bought_price)*amount AS profit
FROM purchase
WHERE purchase_id = 1
This is your query:
SELECT id, SUM(current_price - bought_price)*amount AS profit
FROM purchase
WHERE purchase_id = 1
GROUP BY id ;
If you want the total profit, just remove the group by and rephrase the sum():
SELECT SUM(current_price*amount - bought_price*amount) AS profit
FROM purchase
WHERE purchase_id = 1;
EDIT:
You can also write this as:
SELECT SUM((current_price - bought_price)*amount) AS profit
FROM purchase
WHERE purchase_id = 1;
By the way, the original formulation isn't correct. You want to use the same form with the group by:
SELECT id, SUM((current_price - bought_price)*amount) AS profit
FROM purchase
WHERE purchase_id = 1
GROUP BY id;
The problem with your version is that amount is taken from on arbitrary row for each id. If there is only one row in purchase for each id, then no problem. But if there are multiple rows for an id, then the query in the question will produce indeterminate results. (You are using a MySQL extension to group by that is documented here.)
SELECT id, SUM(current_price - bought_price)*amount
AS profit FROM purchase WHERE purchase_id = 1 GROUP BY id WITH ROLLUP;
You can use WITH ROLLUP modifier for GROUP BY (http://dev.mysql.com/doc/refman/5.0/en/group-by-modifiers.html). As result you will get all summary by product and TOTAL for all product groups.

select case when not working as expected

I'm trying to understand how this works but can't figure it out yet.
I have made this simple uery to test the case-when-then-end clause...
SELECT case when quantity > 3
then count(*) end the_count_a,
case when quantity <= 3
then count(*) end the_count_b
FROM STOCK
my stock table has 30 items with different quantities, only 10 items have quantity over 3 but this is always returning 30.... WHY?
I think it should be returning two columns with values: 10 and 20
Any help will be appreciated!
Thx,
Leo
The value of count(*) means the count of all records (in the current group), regardless of where it is placed. If you want to count records that match a condition, you need to invert your case statement:
select count(case when quantity > 3 then 1 end) the_count_a,
count(case when quantity <= 3 then 1 end) the_count_b
from stock
SELECT
count(case when quantity > 3 then 1 else null end) end the_count_a,
count(case when quantity <= 3 then 1 else null end) end the_count_b
FROM STOCK
The aggregate function COUNT() in absense of a GROUP BY will return all rows in the table which have not been filtered by a WHERE clause. In your case, what you actually need are two subselects or a UNION, depending if you want columns or rows back:
/* Return columns with subselects */
SELECT
(SELECT COUNT(*) FROM STOCK WHERE quantity > 3) AS the_count_a
(SELECT COUNT(*) FROM STOCK WHERE quantity <= 3) AS the_count_b
MySQL is lenient about the presence of a FROM clause, so it can be omitted from the outer query.
/* Return rows instead of columns with UNION */
SELECT
COUNT(*) AS the_count,
'the_count_a'
FROM STOCK WHERE quantity > 3
UNION ALL
SELECT
COUNT(*) AS the_count,
'the_count_b'
FROM STOCK WHERE quantity <= 30