MySQL adding a year over year comparison - mysql

I'm new to SQL and trying to calculate YoY Sales over different stores with quarterly granularity. Table is as follows
So far I have:
SELECT Store_number, SUM(Sales) AS Sales_q1_2018
FROM table1
WHERE Sale_date BETWEEN '2018-01-01' AND '2018-03-31'
GROUP BY Store_number
ORDER BY Sales_Q1_2018
I need to do add a column with the following calculation: (sum(sales q1 2018) - sum(sales q1 2017)) / sum(sales q1 2017)
How can I set different date parameters for a temporary calculation? Thanks

You can use a subquery for retrive the 2017 q1 join these to you actual query
SELECT Store_number, SUM(Sales) AS Sales_q1_2018, (SUM(Sales) - Sales_q1_2107)/Sales_q1_2107
FROM table1
INNER JOIN (
SELECT Store_number, SUM(Sales) AS Sales_q1_2107
FROM table1
WHERE Sale_date BETWEEN '2017-01-01' AND '2017-03-31'
GROUP BY Store_number
) t2 t2.Store_number = table1.Store_number
WHERE Sale_date BETWEEN '2018-01-01' AND '2018-03-31'
GROUP BY Store_number

Try this:
SELECT Store_number,
((SUM(IF(year(Sale_date)='2018',Sales,0))
-SUM(IF(year(Sale_date)='2017',Sales,0)))
/SUM(IF(year(Sale_date)='2017',Sales,0))) Q1_2018_vs_2017
FROM table1
WHERE QUARTER(Sale_date)=1 AND YEAR(Sale_date) IN ('2017','2018')
GROUP BY Store_number;
DEMO ON SQL FIDDLE

Assuming your quarters are calendar quarters, I would write the query as:
SELECT Store_number,
SUM(CASE WHEN YEAR(Sale_date) = 2017 THEN Sales ELSE 0 END) AS Sales_q1_2018,
SUM(CASE WHEN YEAR(Sale_date) = 2018 THEN Sales ELSE 0 END) AS Sales_q1_2017,
(SUM(CASE WHEN YEAR(Sale_date) = 2018 THEN Sales ELSE - Sales END) /
SUM(CASE WHEN YEAR(Sale_date) = 2017 THEN Sales END)
) as calculation
FROM table1
WHERE YEAR(Sale_date) IN (2017, 2018) AND
MONTH(Sale_date) IN (1, 2, 3)
GROUP BY Store_number
ORDER BY Sales_Q1_2018;
Here is the SQL Fiddle.
This is similar to #cdaiga's answer, but with the following important differences:
Functions such as YEAR() return numbers, so the comparisons are to numbers, not strings.
CASE expressions are the ANSI-standard way of including conditional logic in a query. IF() is MySQL-specific.
The ratio protects against division by 0.

Related

Get growth rate per month from transaction table

I have a transaction history like this:
date
revenue
balance
2021-05-03
0
1000
2021-05-21
500
1500
2021-05-23
-250
1250
2021-06-02
-500
750
and I would like to get a result like this:
date
growth
2021-5
0.25
2021-6
-0.4
The formula is:
balance (end of month) - balance (start of month) / balance (start of month)
e.g.: 1250-1000/1000=0.25
and: 750-1250/1250=-0.4
I would very much appreciate a hint for a MYSQL query that is as simple as possible.
You need to pull in the balance from the previous month, if it exists. For that, you can combine lag() with conditional aggregation:
select year(date), month(date),
(-1 +
(max(case when seqnum_desc = 1 then balance end) /
max(case when seqnum_asc = 1 then coalesce(prev_balance, balance) end)
)
) as growth
from (select t.*,
row_number() over (partition by year(date), month(date) order by date) as seqnum_asc,
row_number() over (partition by year(date), month(date) order by date desc) as seqnum_desc,
lag(balance) over (order by date) as prev_balance
from t
) t
group by year(date), month(date);
Here is a db<>fiddle.
#MBauerDC ... Thank You. In my case this was the right direction as I work with MYSQL 5.7. However, a few changes were still necessary to get to the final result:
SELECT
t0.month,
(t2.balance - (t1.balance - t1.revenue)) / (t1.balance - t1.revenue) AS growth
FROM
(SELECT
DATE_FORMAT(date, '%Y-%m') AS 'month',
MIN(date) AS 'min_date',
MAX(date) AS 'max_date'
FROM
t
GROUP BY month) AS t0
JOIN
t AS t1 ON (t1.date = min_date)
JOIN
t AS t2 ON (t2.date = max_date)
The logic of this calculation isn't consistent by your example.
Let's take "start of month" and "end of month" as the record with the first date in the month and the record with the last date in the month.
By this calculation, you do arrive at "0.25" for month 05, but for month 06, there is only one record, so its both the first and last record of the month, and the calculation is (750-750)/750, which is zero.
In your example calculation, you take the end of the last month to calculate the value for month 06, but take the end and start of month 05 for the calculation of the growth in month 05. These are two different calculations! You'll have to decide which one to use.
If you want to use the first and last record in a given month (as you do for month 05), you can use this:
SELECT
`t0`.`month`,
IF(`t1`.balance = 0,
NULL,
((`t2`.balance - `t1`.balance) / `t1`.balance)) AS `growth`
FROM
(SELECT
DATE_FORMAT(`date`, '%Y-%m') AS 'month',
MIN(`date`) AS 'min_date',
MAX(`date`) AS 'max_date'
FROM
`your_table`
GROUP BY MONTH(`date`)) AS `t0`
JOIN
`your_table` AS `t1` ON (`t1`.date = `min_date`)
JOIN
`your_table` AS `t2` ON (`t2`.date = `max_date`);
Note the "IF" because you have to guard against dividing by zero - a growth from zero to any positive value is always "infinity percent", which makes no sense to use - so you have to know how the things you're trying to build should work in these cases.
Provided that the DB version is 8.0, then you can use analytic functions as in the following query
SELECT month,
(bal_end - COALESCE(LAG(bal_end) OVER(ORDER BY month), bal_start)) /
COALESCE(LAG(bal_end) OVER(ORDER BY month), bal_start) AS growth
FROM (SELECT month,
MAX(CASE
WHEN m_end = 1 THEN
sum_balance
END) AS bal_end,
MAX(CASE
WHEN m_start = 1 THEN
sum_balance
END) bal_start
FROM (SELECT month,
SUM(COALESCE(CASE
WHEN bal_start = 1 THEN
balance
END,
0) + COALESCE(revenue, 0)) OVER(ORDER BY date) AS sum_balance,
m_end,
m_start,
date
FROM (SELECT DATE_FORMAT(date, '%Y-%m') AS month,
t.*,
ROW_NUMBER() OVER(PARTITION BY DATE_FORMAT(date, '%Y-%m') ORDER BY date) AS m_start,
ROW_NUMBER() OVER(PARTITION BY DATE_FORMAT(date, '%Y-%m') ORDER BY date DESC) AS m_end,
ROW_NUMBER() OVER(ORDER BY date) AS bal_start
FROM t) AS t0) AS t1
GROUP BY month) AS t2;
month
growth
2021-05
0.2500
2021-06
-0.4000
Demo

Group by not working as expected

Needs help with my SQL query
Query :
Select customer_id,
if (call_details ='International' , 'International Calls', 'National Calls'),
sum(minutes)
from call_minutes
where date between '$from' and '$to'
group by call_details
My Result is displaying as below
Please let me know why National Calls is not getting grouped. I wanted to find the sum of national calls and international calls
use below SQL:
select customer_id,
call_details,
sum(minutes) as minutes
from(
Select customer_id,
if (call_details ='International' , 'International Calls', 'National Calls') as call_details,
minutes
from call_minutes
where date between '$from' and '$to') x
group by customer_id,call_details
You don't need a subquery for this, because MySQL allows you to use column aliases in the group by (not all databases do). So:
Select customer_id,
(case when call_details = 'International'
then 'International Calls'
else 'National Calls'
end) as call_group,
sum(minutes)
from call_minutes
where date between '$from' and '$to'
group by customer_id, call_group;
Note: this assumes that you want separate rows for each customer. If not, remove customer_id from both the select and the group by:
Select (case when call_details = 'International'
then 'International Calls'
else 'National Calls'
end) as call_group,
sum(minutes)
from call_minutes
where date between '$from' and '$to'
group by call_group;
If you want a list of customer ids in each group, you can always add group_concat(customer_id).

Trying to calculate difference between 2 select queries in SQL

Fairly new to SQL but I'm trying to get the difference between 2 select queries from the same Table. I have tried the following
SELECT
(SELECT KwhMeter,IndexElek,CalorieMeter,IndexWarmte,IndexWarmWater,IndexKoudWater,Date FROM Energiemeters WHERE Date = '2017-05-01')
-
(SELECT KwhMeter,IndexElek,CalorieMeter,IndexWarmte,IndexWarmWater,IndexKoudWater,Date FROM Energiemeters WHERE Date = '2017-04-01') AS Difference
but I end up having the following error :
#1241 - Operand should contain 1 column(s)
If you want rows that are on May 1st but not April 1st, then one way is to use aggregation:
SELECT KwhMeter, IndexElek, CalorieMeter, IndexWarmte, IndexWarmWater, IndexKoudWater, Date
FROM Energiemeters
WHERE Date IN ('2017-04-01', '2017-05-01')
GROUP BY KwhMeter, IndexElek, CalorieMeter, IndexWarmte, IndexWarmWater, IndexKoudWater
HAVING MIN(Date) = '2017-05-01';
Using Cross Join. This is with the assumption that you get only 1 row per date.
Select
(a.KwhMeter-b.KwhMeter) as KwhMeter,
(a.IndexElek-b.IndexElek) as IndexElek,
(a.CalorieMeter-b.CalorieMeter) CalorieMeter,
(a.IndexWarmte-b.IndexWarmte) IndexWarmte,
(a.IndexWarmWater-b.IndexWarmWater) IndexWarmWater,
(a.IndexKoudWater-b.IndexKoudWater) IndexKoudWater,
(a.Date-b.Date) as Date
from
(
SELECT distinct KwhMeter,IndexElek,CalorieMeter,IndexWarmte,IndexWarmWater,IndexKoudWater,Date
FROM Energiemeters
WHERE Date = '2017-05-01'
) a
cross join
(
SELECT distinct KwhMeter,IndexElek,CalorieMeter,IndexWarmte,IndexWarmWater,IndexKoudWater,Date
FROM Energiemeters
WHERE Date = '2017-04-01'
) b;
It seems as though you want to subtract the respective values of columns from two rows determined by dates 2017-05-01 and 2017-04-01?
If yes, then the query can be written as follows:
SELECT SUM(CASE Date
WHEN '2017-05-01' THEN KwhMeter
WHEN '2017-04-01' THEN -KwhMeter
END) AS KwhMeter,
SUM(CASE Date
WHEN '2017-05-01' THEN IndexElek
WHEN '2017-04-01' THEN -IndexElek
END) AS IndexElek,
SUM(CASE Date
WHEN '2017-05-01' THEN CalorieMeter
WHEN '2017-04-01' THEN -CalorieMeter
END) AS CalorieMeter,
SUM(CASE Date
WHEN '2017-05-01' THEN IndexWarmte
WHEN '2017-04-01' THEN -IndexWarmte
END) AS IndexWarmte,
SUM(CASE Date
WHEN '2017-05-01' THEN IndexWarmWater
WHEN '2017-04-01' THEN -IndexWarmWater
END) AS IndexWarmte,
SUM(CASE Date
WHEN '2017-05-01' THEN IndexKoudWater
WHEN '2017-04-01' THEN -IndexKoudWater
END) AS IndexKoudWater
FROM Energiemeters
WHERE Date IN ('2017-05-01', '2017-04-01')
A small scale working demo can be found here.
WORKING IN MOST RDBMSs EXCEPT MYSQL:
If I wanted to compute per-column difference I would use common table expressions to prepare subresults and then compute difference.
WITH
res1 AS
(SELECT KwhMeter,IndexElek,CalorieMeter,IndexWarmte,IndexWarmWater,IndexKoudWater,Date FROM Energiemeters WHERE Date = '2017-05-01'),
res2 AS
(SELECT KwhMeter,IndexElek,CalorieMeter,IndexWarmte,IndexWarmWater,IndexKoudWater,Date FROM Energiemeters WHERE Date = '2017-04-01')
SELECT
r1.KwhMeter - r2.KwhMeter, r1.OtherColumnName - r2.OtherColumnName ... FROM res1 r1, res2 r2
However ... This works perfectly on 1 row per subselect (date). Do you guarentee one entry per date? Is that a PK? You need to specify your question, mainly what do you mean by 'difference'.

SQL query summary issue

I'm new to SQL and trying to create a total summary of a working SQL query. It's listing the total results from one month of data.
Now I need the total values of the outcome of the query.
So I created a 'query in a query' piece of SQL, but it ain't working because my lack of SQL knowledge. I guess it's an easy fix for you pro's :-)
The working SQL query with the daily outcome of one month:
SELECT
DATE_FORMAT(date, '%d/%m/%y') AS Datum,
COUNT(*) AS Berichten,
SUM(CASE WHEN virusinfected>0 THEN 1 ELSE 0 END) AS Virus,
SUM(CASE WHEN (virusinfected=0 OR virusinfected IS NULL) AND isspam>0 THEN 1 ELSE 0 END) AS Ongewenst,
SUM(CASE WHEN (virusinfected=0 OR virusinfected IS NULL) AND (isspam=1) AND isrblspam>0 THEN 1 ELSE 0 END) AS RBL,
SUM(size) AS Grootte
FROM
maillog
WHERE
1=1
AND (1=1)
AND
date < '2017-04-01'
AND
date >= '2017-03-01'
AND
to_domain = 'domain1.nl'
OR
date < '2017-04-01'
AND
date >= '2017-03-01'
AND
to_domain = 'domain2.nl'
GROUP BY
Datum
ORDER BY
date
The incorrect query trying to create the monthly totals:
SELECT Datum,
SUM(Berichten) AS Berichten,
SUM(Virus) AS Virus,
SUM(Ongewenst) AS Ongewenst,
SUM(RBL) AS RBL,
SUM(Grootte) AS Grootte,
FROM ( SELECT
DATE_FORMAT(date, '%d/%m/%y') AS Datum,
COUNT(*) AS Berichten,
SUM(CASE WHEN virusinfected>0 THEN 1 ELSE 0 END) AS Virus,
SUM(CASE WHEN (virusinfected=0 OR virusinfected IS NULL) AND isspam>0 THEN 1 ELSE 0 END) AS Ongewenst,
SUM(CASE WHEN (virusinfected=0 OR virusinfected IS NULL) AND (isspam=1) AND isrblspam>0 THEN 1 ELSE 0 END) AS RBL,
SUM(size) AS Grootte
FROM
maillog
WHERE
1=1
AND (1=1)
AND
date < '2017-04-01'
AND
date >= '2017-03-01'
AND
to_domain = 'domain1.nl'
OR
date < '2017-04-01'
AND
date >= '2017-03-01'
AND
to_domain = 'domain2.nl'
GROUP BY
Datum
ORDER BY
date
) t
GROUP BY Datum;
Thanks in advance.
What you want can be done with just a little addition to your first SQL statement: add with rollup after the group by clause:
GROUP BY Datum WITH ROLLUP
It will run more efficiently than the version with sub-query, although it could work that way, but you should then remove the outer group by clause and not select Datum there, since you don't want the totals per date any more, but overall.
Still, you will lose the details and only get the overall totals then. You would have to use a union with your original query to get both levels of totals. You can imagine that the with rollup modifier will do the job more efficiently.

Find first buisness day of next month MySQL without function

We have a date_value column and another Boolean column which indicates whether the day is a business day or not.
We are trying to find the first business day of the next month( example, for September, 2015 I want it to return 2015-10-01)
We have tried a couple different methods involving last_day, intervals and subqueries but can't quite get it to work.
We also don't have the ability to create custom functions, which makes this a little more difficult.
I think you want something like this:
select min(date_value) fwd
from tablename
where isWorkDay = 1 and
extract(year from date_value)=extract(year from curdate()) and
extract(month from date_value)=extract(month from curdate()) + 1
For all months (v0.3) (please note that I can test this now, so it might have some error):
select t1.month_number, min(t2.date_value)
from tablename t1 join
tablename t2 on extract(year from t1.date_value) * 12 + t1.month_number = extract(year from t2.date_value) * 12 + t2.month_number - 1
where t2.isWorkDay = 1
group by t1.month_number
I was able to get it using the below
SELECT
d.year
,d.month_number
,first_business_period as next_month_first_period
,d2.previous_business_day
FROM lk_date d
JOIN (
SELECT
a.*
, MAX(CASE WHEN d2.business_period_in_days<>0 THEN d2.date_value ELSE NULL END) AS previous_business_day
FROM(
SELECT
d1.year
,d1.month_number
, MIN(CASE WHEN d1.business_period_in_days <> 0 THEN d1.date_value END) AS first_business_period
FROM lk_date d1
GROUP BY 1,2
) a
JOIN lk_date d2 ON d2.date_Value < a.first_business_period
GROUP BY 1,2,3) d2 on d2.previous_business_day = d.date_value