How to calculate percent increase/decrease using previous value - mysql

I have a query that returns total value of sales grouped by month. I would like to add a column that returns the growth/decrease in percentage comparing with previous rows.
I have tried already to use CREATE TEMPORARY TABLE, creating a variable #var :=, but nothing is really working. Ideally my result would be:
Month | Sales | Perc
1 | 100 | 0
2 | 150 | 50
3 | 100 | -33.33
The calculation to be done is: ((actual_value - previous_value) / previous_value) *100
About the tables, I working with sales, products, sales_items, so my query to retrieve the total amount of sales grouped by month is:
SELECT
MONTH(s.sale_date) AS Month,
SUM(p.retail_price * si.quantity) AS Sales_Amount
FROM sales s
INNER JOIN sales_items si ON s.sale_id = si.sale_id
INNER JOIN products p ON si.product_id = p.product_id
WHERE YEAR(s.sale_date) = '2018'
GROUP BY month
ORDER BY month;
As I'm using SUM to summarise the items of the sales and aggregating that into each sale_id, and after grouping by month, it seems complicated to use that value into a new column, so it would be great if someone has some idea how to do it.

In MySQL 8.0, you can turn the aggregate query to a subquery and use window function LAG to access the value of the previous month, like:
SELECT
Sales_Month,
Sales_Amount,
( Sales_Amount - LAG(Sales_Amount) OVER(ORDER BY Sales_Month) )
/LAG(Sales_Amount) OVER(ORDER BY Sales_Month) * 100 AS Percent_Increase
FROM (
SELECT
MONTH(s.sale_date) AS Sales_Month,
SUM(p.retail_price * si.quantity) AS Sales_Amount
FROM sales s
INNER JOIN sales_items si ON s.sale_id = si.sale_id
INNER JOIN products p ON si.product_id = p.product_id
WHERE YEAR(s.sale_date) = '2018'
GROUP BY Sales_Month
) x
ORDER BY Sales_Month

You can add a new column "PrevMonthSales". To do that you have to join your query with itself :
SELECT ...
FROM
(...your_query...) s1
LEFT JOIN (...your_query...) s2 ON s2.Month = s1.Month - 1
In your case:
SELECT s1.Month, s1.Sales_Amount,
COALESCE(s2.Sales_Amount, 0) "PrevMonthSales",
CASE
WHEN s1.Sales_Amount = 0 THEN 0
ELSE (s1.Sales_Amount - COALESCE(s2.Sales_Amount, 0)) / s1.Sales_Amount
END "Percent"
FROM
(...your_query...) s1
LEFT JOIN (...your_query...) s2 ON s2.Month = s1.Month - 1

Make below a temp table called month_sales
SELECT
MONTH(s.sale_date) AS Month,
SUM(p.retail_price * si.quantity) AS Sales_Amount
FROM sales s
INNER JOIN sales_items si ON s.sale_id = si.sale_id
INNER JOIN products p ON si.product_id = p.product_id
WHERE YEAR(s.sale_date) = '2018'
GROUP BY month
ORDER BY month;
Query to find month over month difference is :
SELECT *,
case
when m2.Sales_Amount is null then 0
else (m2.Sales_Amount - m1.Sales_Amount)/m1.Sales_Amount
end as Perc
FROM month_sales m1
left Join month_sales m2
WHERE m1.month = m2.month - 1

Related

Sum columns with similar value- SQL

I need to sum all fields that have similar value.
I have a simple SQL query that give me back this result:
For example, the name like 'Climatizzazione' has to be sum to 'Climatizzatori Samsung' and 'Climatizzatori Daiki' and get a unique result.
The final result would be like that:
| name | totale_fatturato |
| -------- | -------------- |
| Climatizzazione | 535.241,583465|
| Scaldabagni| 90680,77684|
| Differenziali e Magnetotermici| 78511,185704|
ecc.......
This is my SQL query:
SELECT
cl.name,
SUM(od.total_price_tax_excl) AS totale_fatturato
FROM `ww_ps_order_detail` AS `od`
INNER JOIN `ww_ps_product` AS p ON od.product_id = p.id_product
INNER JOIN `ww_ps_category_lang` AS cl ON p.id_category_default = cl.id_category
WHERE od.ID_ORDER IN (
SELECT ID_ORDER
FROM ww_ps_orders
WHERE 1=1 AND DATE_ADD >= DATE_SUB(NOW(), INTERVAL 30 DAY)) AND cl.id_lang = 1
GROUP BY name
ORDER BY totale_fatturato DESC
LIMIT 10
What i have to do for get the result i want? Thx!
Thanks to Akina, Jonas Metzler and Trillion, i found my solution.
So, how you have suggest me, i create a supplementary column with some cases where i can handle all the exceptions and group by this column.
This is my result:
SELECT
cl.name AS NAME,
SUM(od.total_price_tax_excl) AS `totale_fatturato`,
CASE
WHEN cl.name LIKE 'Climatizza%' THEN 'Climatizzazione'
/* SOME OTHER CASES */
ELSE cl.name
END AS groupName
FROM `ww_ps_order_detail` AS `od`
INNER JOIN `ww_ps_product` AS p ON od.product_id = p.id_product
INNER JOIN `ww_ps_category_lang` AS cl ON p.id_category_default = cl.id_category
WHERE od.ID_ORDER IN (
SELECT ID_ORDER
FROM ww_ps_orders
WHERE 1=1 AND DATE_ADD >= DATE_SUB(NOW(), INTERVAL 30 DAY)) AND cl.id_lang = 1
GROUP BY groupName
ORDER BY totale_fatturato DESC

Getting percentage of total in SQL with two joins

So I'm trying to do something that I think should be fairly simple with SQL. But I'm having a hard time figuring it out. Here is the format of my data:
One table with user information, let's call it User:
ID name_user Drive_Type
1 Tim Stick shift
2 Jim Automatic
3 Bob Automatic
4 Lisa Stick shift
Then I have one table used for the join, let's call it Join_bridge:
user_ID car_has_ID
1 12
2 13
3 14
4 14
And one table with car information, let's call it Car:
car_ID name
12 Honda
13 Toyota
14 Ford
Then what I want is something that looks like this with the total number of Ford's that are stick shift and the percentage
name Total percentage
Ford 1 25%
I have tried the following, which gets the total right, but not the percentage:
select Drive_Type,
name,
count(Drive_Type) as Total,
(count(Drive_Type) / (select count(*)
from User
join Join_bridge
on User.ID = user_ID
join Car
on Car.car_ID = Join_bridge.car_has_ID
) * 100.0 as Percent
from User
join Join_bridge
on User.ID = Join_bridge.user_ID
join Car
on Car.car_ID = Join_bridge.car_has_ID
where name = 'Ford' and Drive_Type = "Automatic"
;
What am I missing? Thanks.
See this SQL Fiddle with the query - the trick is to SUM over CASE that returns 1 for rows you look for and 0 for the rest in order to calculate "Total" at the same time you can also count all rows to calculate percentage.
Here's the SQL query:
SELECT
'Ford' name,
SUM(a.ford_with_stack_flag) Total,
100.0 * SUM(a.ford_with_stack_flag) / COUNT(*) percentage
FROM (
SELECT
Car.name,
(CASE WHEN User.Drive_Type = 'Stick Shift' and Car.name = 'Ford' THEN 1 ELSE 0 END) ford_with_stack_flag
FROM User
JOIN Join_bridge on User.ID = Join_bridge.user_ID
JOIN Car ON Car.car_ID = Join_bridge.car_has_ID
) a
Compute percent and join to Car. Window functions are supported in MySql 8.0
select c.car_ID, c.name, p.cnt, p.Percent
from car c
join (
select car_has_ID, u.Drive_Type,
count(*) cnt,
count(*) / count(count(*)) over() Percent
from Join_bridge b
join user u on u.ID = b.user_ID
group by b.car_has_ID, u.Drive_Type
) p on p.car_has_ID = c.car_ID
where c.name = 'Ford' and p.Drive_Type='Stick shift';
db<>fiddle

Mysql subquery issues

Here is my mysql schema and query.
http://sqlfiddle.com/#!2/73b0d/2
I want sum(each day's memo.discount), date, sum(each day's sale sum(item.sell_price)) in each row. But can't seem to find out the way. How can I do this?
Expected outcome.
total_discount | added_on | total_sale
300 | 2014-06-25 00:00:00 | 1580
230 | 2014-06-26 00:00:00 | 980
Thanks in advance.
SELECT
SUM(m.discount) AS total_discount,
m.added_on,
sub0.total_sold AS total_sale
FROM memo m
LEFT OUTER JOIN
(
SELECT DATE(memo.added_on) AS group_added_on, SUM(item.sell_price) AS total_sold
FROM memo
JOIN memo_item ON memo_item.memo_id = memo.id
JOIN item ON item.id = memo_item.item_id
WHERE memo.showroom_id = 2
GROUP BY group_added_on
) sub0
ON group_added_on = DATE(m.added_on)
WHERE m.showroom_id = 2 and m.added_on BETWEEN '2014-06-25' AND '2014-06-26'
GROUP BY m.added_on
This has a sub query that gets the sum of the selling prices for each day for the showroom_id you are interested in, then joins that back against the memo table.
How about this?
select
sum(m.discount) as total_discount,
m.added_on,
sum(item.sell_price) as total_sale
from memo m
join memo_item on memo_item.memo_id = m.id
join item on item.id = memo_item.item_id
where m.showroom_id = 2 and m.added_on between '2014-06-25' and '2014-06-26'
group by m.added_on
Edit: to use discount only from "memo" table... :
select
sum(discount) as total_discount,
added_on,
sum(subtotal_sale) as total_sale
from
(
select
m.discount,
m.added_on,
sum(item.sell_price) as subtotal_sale
from memo m
join memo_item on memo_item.memo_id = m.id
join item on item.id = memo_item.item_id
where m.showroom_id = 2 and m.added_on between '2014-06-25' and '2014-06-26'
group by m.id
) h
group by added_on

making an unique column value after order by mysql

So, my problem is that I have a list of customers (table now has around 100k records) with income per each customer. When I group it by country I get around 60 countries with sum of income. Than I need to order it by the income DESC, my query looks something like this:
SELECT s2.i,s1.year,s1.short_c,s1.country,s1.uges FROM
(SELECT u.year,k.short_c,s.country, IFNULL(ROUND(SUM(u.income)),0) as uges
FROM im_income u,im_contact k,td_countries s
WHERE u.year=2012
AND u.customer_id=k.id
AND k.kat='K'
AND k.short_c=s.short_c
GROUP BY k.short_c, u.year
ORDER BY u.year ASC,uges DESC) s1
CROSS JOIN
(SELECT #i:=#i+1 as i FROM (SELECT #i:= 0) AS i) s2
And I know that this with CROSS JOIN is wrong since it is not giving me what I need, but is there a way to make an unique id after ORDER BY since I need to order countries with income DESC and than assing them id that would represent a rank number???
Result looks like this now:
+-+----+-------+---------+------+
|i|year|short_c|country |uges |
+-+----+-------+---------+------+
|1|2012|USA |United S.|123456|
+-+----+-------+---------+------+
|1|2012|RU |Russia |23456 |
+-+----+-------+---------+------+
And I would want it in this way, but to assign after order by the unique i value:
+-+----+-------+---------+------+
|i|year|short_c|country |uges |
+-+----+-------+---------+------+
|1|2012|USA |United S.|123456|
+-+----+-------+---------+------+
|2|2012|RU |Russia |23456 |
+-+----+-------+---------+------+
|3| | | | |
+-+----+-------+---------+------+
Any help would be appreciated.
I think this is what you are looking for:
SELECT #i := #i + 1 as i, s1.year, s1.short_c, s1.country, s1.uges
FROM (SELECT u.year,
k.short_c,
s.country,
IFNULL(ROUND(SUM(u.income)),0) as uges
FROM im_income u join
im_contact k
on u.customer_id = k.id join
td_countries s
on k.short_c = s.short_c
WHERE u.year = 2012 AND k.kat = 'K'
GROUP BY k.short_c, u.year
) s1
CROSS JOIN
(SELECT #i:= 0) const
ORDER BY year, uges desc;
The variable evaluation occurs when the results are being "output", after the order by.
I also fixed your join syntax. You should learn to use the explicit join rather than implicit joins in the where clause.

Sum of columns from tables in join

I have 3 tables, Orders, Orders_products and Orders_total. Currently i have a query that gets the SUM of products for each months, but now we would like to also add the freight cost that is in a different table.
I tried with the following that returned the correct total_value, but the total_shipping is 5 times as big. This i think has to due with that orders, can have multiplie products, but i cant figure out what else to do.
SELECT Count(DISTINCT O.orders_id) AS Orders,
Sum(OP.final_price * OP.products_quantity) AS total_value,
Date_format(O.last_modified, '%m-%Y') AS date_interval,
Sum(OT.value) AS total_shipping
FROM
orders AS O
LEFT JOIN
orders_total AS OT
ON ( OT.orders_id = O.orders_id
AND OT.class = 'ot_shipping' ),
orders_products AS OP
WHERE
( O.orders_id = OP.orders_id )
AND ( O.orders_status = 3 )
GROUP BY date_interval
ORDER BY O.last_modified DESC
The returned value is:
+----+------------+---------------+----------------+
| ID | total_value| date_interval | total_shipping |
+----+------------+---------------+----------------+
| 17 | 55912.2160 | 01-2014 | 24954 |
Expected:
+----+------------+---------------+----------------+
| ID | total_value| date_interval | total_shipping |
+----+------------+---------------+----------------+
| 17 | 55912.2160 | 01-2014 | 4938 |
Here is the sqlfiddle http://sqlfiddle.com/#!2/dfe10/1/0
It contains one order, with 3 products in it. the expected total_value is 500 and the expected total_shipping is also 500, but returns 1500 (3 x products). Sadly i had to remove a lot of fields from my table due to a limit in sqlfiddle of max 8000 chars.
Try putting the shipping value into an inline view:
select count(*) as Orders,
sum(ord.order_total_value) as total_value,
ord.date_interval as date_interval,
sum(ship.order_shipping_value) as total_shipping
from
(
SELECT O.orders_id,
O.last_modified AS modified_date,
Sum(OP.final_price * OP.products_quantity) as order_total_value,
Date_format(O.last_modified, '%m-%Y') as date_interval
FROM orders AS O
INNER JOIN orders_products AS OP on O.orders_id = OP.orders_id
WHERE O.orders_status = 3
GROUP BY date_interval,O.orders_id
) ord LEFT OUTER JOIN
(
SELECT orders_id,sum(value) as order_shipping_value
FROM orders_total
WHERE class='ot_shipping'
GROUP BY orders_id
) ship ON ord.orders_id = ship.orders_id
GROUP BY ord.date_interval
ORDER BY modified_date DESC;