Rolling sum for a column in mysql - mysql

I would like to get the rolling sum for the growth column for the following sample data which can be found here dbfiddle
The output should look like,
Growth RollingSum TMonth TYear
511 511 AUG 2019
79 590 SEP 2019
-6 584 OCT 2019
0 584 NOV 2019
-4 580 DEC 2019
45 625 JAN 2020
-1 624 FEB 2020
7 631 MAR 2020
-22 609 APR 2020
-6 603 MAY 2020
-20 583 JUN 2020
0 583 JUL 2020
My attempt is as follows. I am only getting the total for values in the Growth column
SELECT r1.Growth, sum(r2.Growth) AS rolling_total ,r1.Month,r1.Year
FROM Report AS r1 JOIN Report AS r2
ON r1.Month = r1.Month
GROUP BY r1.Month;
The above gives me the result as,
Growth RollingSum TMonth TYear
511 583 AUG 2019
79 583 SEP 2019
-6 583 OCT 2019
0 583 NOV 2019
-4 583 DEC 2019
45 583 JAN 2020
-1 583 FEB 2020
7 583 MAR 2020
-22 583 APR 2020
-6 583 MAY 2020
-20 583 JUN 2020
0 583 JUL 2020
I want to avoid using procedures,cursors or functions if at all possible. I am using mysql 8.0.17

You can use SUM() window function but with the correct ordering.
This ordering:
ORDER BY DATE_FORMAT(`TMonth`, '%M'), `TYear`
is wrong because DATE_FORMAT() with these parameters returns NULL, so you simply sort by Year.
See the demo.
It is coincidental that you get correct results.
The correct ordering is:
ORDER BY STR_TO_DATE(CONCAT(TYear, TMonth, '01'), '%Y%b%d')
So use this:
SELECT
Growth,
SUM(Growth) OVER (ORDER BY STR_TO_DATE(CONCAT(TYear, TMonth, '01'), '%Y%b%d')) RollingSum,
TMonth,
TYear
FROM Report
See the demo.
Results:
> Growth | RollingSum | TMonth | TYear
> -----: | ---------: | :----- | ----:
> 511 | 511 | AUG | 2019
> 79 | 590 | SEP | 2019
> -6 | 584 | OCT | 2019
> 0 | 584 | NOV | 2019
> -4 | 580 | DEC | 2019
> 45 | 625 | JAN | 2020
> -1 | 624 | FEB | 2020
> 7 | 631 | MAR | 2020
> -22 | 609 | APR | 2020
> -6 | 603 | MAY | 2020
> -20 | 583 | JUN | 2020
> 0 | 583 | JUL | 2020

Since you are using mysql 8 use window functionn SUM
DATE_FOMAT are not working, so you need STR_TO_DATE
CREATE TABLE roll_month (
`Growth` INTEGER,
`RollingSum` INTEGER,
`TMonth` VARCHAR(3),
`TYear` INTEGER
);
INSERT INTO roll_month
(`Growth`, `RollingSum`, `TMonth`, `TYear`)
VALUES
('511', '511', 'AUG', '2019'),
('79', '590', 'SEP', '2019'),
('-6', '584', 'OCT', '2019'),
('0', '584', 'NOV', '2019'),
('-4', '580', 'DEC', '2019'),
('45', '625', 'JAN', '2020'),
('-1', '624', 'FEB', '2020'),
('7', '631', 'MAR', '2020'),
('-22', '609', 'APR', '2020'),
('-6', '603', 'MAY', '2020'),
('-20', '583', 'JUN', '2020'),
('0', '583', 'JUL', '2020');
SELECT
`Growth`
, SUM(`Growth`) OVER( ORDER BY STR_TO_DATE(CONCAT('01-',`TMonth`,'-',`TYear`), '%d-%M-%Y') ROWS UNBOUNDED PRECEDING) RollingSum
, `TMonth`, `TYear`
FROM roll_month
Growth | RollingSum | TMonth | TYear
-----: | ---------: | :----- | ----:
511 | 511 | AUG | 2019
79 | 590 | SEP | 2019
-6 | 584 | OCT | 2019
0 | 584 | NOV | 2019
-4 | 580 | DEC | 2019
45 | 625 | JAN | 2020
-1 | 624 | FEB | 2020
7 | 631 | MAR | 2020
-22 | 609 | APR | 2020
-6 | 603 | MAY | 2020
-20 | 583 | JUN | 2020
0 | 583 | JUL | 2020
db<>fiddle here

With the values and names that i took from your table, this cross join would give you the output you want way more easy:
select r1.Growth, r1.TMonth, TYear,
(#s := #s + r1.Growth) as RollingSum
from Report r1 cross join
(select #s := 0) p;

Related

Add rows for each day that lies in a period in MYSQL

I am working on a table 'booking' -
I want to add a column 'date_of_stay' to this table where date_of_stay will store each date in the period that a booking_id will stay for as per the number of nights given by column 'nights'.
FOR eg-
booking_id booking_date nights date_of_stay
5001 Thu, 03 Nov 2016 7 Thu, 03 Nov 2016
5001 Thu, 03 Nov 2016 7 Fri, 04 Nov 2016
5001 Thu, 03 Nov 2016 7 Sat, 05 Nov 2016
5001 Thu, 03 Nov 2016 7 Sun, 06 Nov 2016
5001 Thu, 03 Nov 2016 7 Mon, 07 Nov 2016
5001 Thu, 03 Nov 2016 7 Tue, 08 Nov 2016
5001 Thu, 03 Nov 2016 7 Wed, 09 Nov 2016
5002 Thu, 03 Nov 2016 2 Thu, 03 Nov 2016
5002 Thu, 03 Nov 2016 2 Fri, 04 Nov 2016
What can be the simplest way of viewing my table like this without altering it?
Use a recursive CTE:
with cte as (
select booking_id, booking_date, nights, 1 as n
from t
union all
select booking_id, booking_date, nights, 1 + n
from cte
where n < nights
)
select *
from cte;
According this article you can use next query:
select booking.*, date_of_stay
from (
select adddate('2015-01-01', t3*1000 + t2*100 + t1*10 + t0) date_of_stay from
(select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3
) v
join booking ON date_of_stay between
booking.booking_date and date_add(booking.booking_date, interval nights-1 day)
order by booking_id, date_of_stay
;
The above query works in MYSQL 5 & 8 as well. It is valid for all dates in interval:
select '2015-01-01' from_date, adddate('2015-01-01',9*1000 + 9*100 + 9*10 + 9) to_date;
+------------+------------+
| from_date | to_date |
+------------+------------+
| 2015-01-01 | 2042-05-18 |
+------------+------------+
Here query can be tested SQLize.online
You can do it with a recursive CTE:
WITH RECURSIVE cte AS (
SELECT booking_id, booking_date, nights, 0 nr,
STR_TO_DATE(booking_date, '%a, %d %b %Y') date_of_stay
FROM booking
UNION ALL
SELECT booking_id, booking_date, nights, nr + 1,
date_of_stay + interval 1 day
FROM cte
WHERE nr < nights - 1
)
SELECT c.booking_id, c.booking_date, c.nights,
DATE_FORMAT(c.date_of_stay, '%a, %d %b %Y') date_of_stay
FROM cte c
ORDER BY c.booking_id, c.date_of_stay
See the demo.
Results:
> booking_id | booking_date | nights | date_of_stay
> ---------: | :--------------- | -----: | :---------------
> 5001 | Thu, 03 Nov 2016 | 7 | Thu, 03 Nov 2016
> 5001 | Thu, 03 Nov 2016 | 7 | Fri, 04 Nov 2016
> 5001 | Thu, 03 Nov 2016 | 7 | Sat, 05 Nov 2016
> 5001 | Thu, 03 Nov 2016 | 7 | Sun, 06 Nov 2016
> 5001 | Thu, 03 Nov 2016 | 7 | Mon, 07 Nov 2016
> 5001 | Thu, 03 Nov 2016 | 7 | Tue, 08 Nov 2016
> 5001 | Thu, 03 Nov 2016 | 7 | Wed, 09 Nov 2016
> 5002 | Thu, 03 Nov 2016 | 2 | Thu, 03 Nov 2016
> 5002 | Thu, 03 Nov 2016 | 2 | Fri, 04 Nov 2016

MySQL String to DATE / TIME or TIMESTAMP

In my data set, the start and end time for a task is given as a string. The string contains:
'Day, Date Month YYYY HH:MM:SS GMT'
'Wed, 18 Oct 2017 10:11:03 GMT'
The previous questions on Stack Overflow do not have data in this format and I have been struggling how to convert it into DATE/TIME or TIMESTAMP. Any advice on this would be greatly appreciated!
This post was quite relevant but still does not meet my needs, as the format of the string is different in both cases:
Converting date/time string to unix timestamp in MySQL
Overall, I want to achieve a variable 'time_on_task' which takes the difference per person between their start_time and end_time. Thus, for the following data:
Person TaskID Start_time End_time
Alpha 1 'Wed, 18 Oct 2017 10:10:03 GMT' 'Wed. 18 Oct 2017 10:10:36 GMT'
Alpha 2 'Wed, 18 Oct 2017 10:11:16 GMT' 'Wed, 18 Oct 2017 10:11:28 GMT'
Beta 1 'Wed, 18 Oct 2017 10:12:03 GMT' 'Wed, 18 Oct 2017 10:12:49 GMT'
Alpha 3 'Wed, 18 Oct 2017 10:12:03 GMT' 'Wed, 18 Oct 2017 10:13:13 GMT'
Gamma 1 'Fri, 27 Oct 2017 22:57:12 GMT' 'Sat, 28 Oct 2017 02:00:54 GMT'
Beta 2 'Wed, 18 Oct 2017 10:13:40 GMT' 'Wed, 18 Oct 2017 10:14:03 GMT'
The required output would be something like this:
Person TaskID Time_on_task
Alpha 1 0:00:33 #['Wed, 18 Oct 2017 10:10:36 GMT' - 'Wed, 18 Oct 2017 10:10:03 GMT']
Alpha 2 0:00:12 #['Wed, 18 Oct 2017 10:11:28 GMT' - 'Wed, 18 Oct 2017 10:11:16 GMT']
Beta 1 0:00:46 #['Wed, 18 Oct 2017 10:12:49 GMT' - 'Wed, 18 Oct 2017 10:12:03 GMT']
Alpha 3 0:01:10 #['Sat, 18 Nov 2017 10:13:13 GMT' - 'Sat, 18 Nov 2017 10:12:03 GMT']
Gamma 1 3:03:42 #['Sat, 28 Oct 2017 02:00:54 GMT' - 'Fri, 27 Oct 2017 22:57:12 GMT']
Beta 2 0:00:23 #['Wed, 18 Oct 2017 10:14:03 GMT' - 'Wed, 18 Oct 2017 10:13:40 GMT']
You need STR_TO_DATE() to convert the string to a date. Consider:
select str_to_date(
'Wed, 18 Oct 2017 10:11:03 GMT',
'%a, %d %b %Y %T GMT'
)
Yields:
2017-10-18 10:11:03
Once you strings are converted to dates, you can use timestampdiff() to compute the difference between them, and turn the result back to a time using sec_to_time():
select
person,
taskID,
sec_to_time(
timestampdiff(
second,
str_to_date(Start_time, '%a, %d %b %Y %T GMT'),
str_to_date(End_time, '%a, %d %b %Y %T GMT')
)
) time_on_task
from mytable
Demo on DB Fiddlde:
| person | taskID | time_on_task |
| ------ | ------ | ------------ |
| Alpha | 1 | 00:00:33 |
| Alpha | 2 | 00:00:12 |
| Beta | 1 | 00:00:46 |
| Alpha | 3 | 00:01:10 |
| Gamma | 1 | 03:03:42 |
| Beta | 2 | 00:00:23 |

MySQL Complex Check with dates

I have a table called Offers in which I have several offers:
ID | List Name | Arrival | Depart | Price
1 | Plus | 12 August | 18 August | $ 100.00
2 | Plus | 19 August | 25 August | $ 120.00
3 | Plus | 26 August | 1 September | $ 80.00
4 | Weekend | 11 August | 13 August | $ 50.00
5 | Weekend | 18 August | 20 August | $ 60.00
6 | Weekend | 25 August | 27 August | $ 40.00
Then I have a guest in my hotel which has a check-in and check-out date.
In need to find all the offers with the same List Name that cover all the days of my vacation's guest (from check-in date to check-out date).
Example
If my guest spends his time in my hotel from the 13th of August to the 20th of August I need to be returned only ids 1 and 2.
If my guest spends his time in my hotel from the 13th of August to the 27th of August I need to be returned only ids 1, 2, and 3. That's because the ids 6 and 7 cover only weekend and not all the days between check-in and check-out dates.
If my guest spends his time in my hotel from the 18th of August to the 20th of August I need to be returned only ids 1, 2 and 5.
You are looking for logic like this:
select o.*
from offers o
where o.arrival <= $arrival and o.depart >= $depart;
$arrival and $depart are the dates for your guest.
You need to do something like this :
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE offers
(`ID` int, `Arrival` date, `Depart` date, `Price` float)
;
INSERT INTO offers
(`ID`, `Arrival`, `Depart`, `Price`)
VALUES
(1, '2016-08-12', '2016-08-18', 100.00),
(2, '2016-08-19', '2016-08-25', 120.00),
(3, '2016-08-26', '2016-09-01', 80.00),
(4, '2016-08-11', '2016-08-13', 50.00),
(5, '2016-08-18', '2016-08-20', 60.00),
(6, '2016-08-25', '2016-08-27', 40.00)
;
Query 1:
select *
from offers
where (((arrival >= '2016-08-13' and arrival <= '2016-08-20') or
(depart >= '2016-08-13' and depart <= '2016-08-20'))
and datediff(depart,arrival) = 6)
or
(arrival = '2016-08-13' and depart = '2016-08-20'
and datediff(depart,arrival) = 2)
Results:
| ID | Arrival | Depart | Price |
|----|--------------------------|--------------------------|-------|
| 1 | August, 12 2016 00:00:00 | August, 18 2016 00:00:00 | 100 |
| 2 | August, 19 2016 00:00:00 | August, 25 2016 00:00:00 | 120 |
Query 2:
select *
from offers
where (((arrival >= '2016-08-13' and arrival <= '2016-08-27') or
(depart >= '2016-08-13' and depart <= '2016-08-27'))
and datediff(depart,arrival) = 6)
or
(arrival = '2016-08-13' and depart = '2016-08-27'
and datediff(depart,arrival) = 2)
Results:
| ID | Arrival | Depart | Price |
|----|--------------------------|-----------------------------|-------|
| 1 | August, 12 2016 00:00:00 | August, 18 2016 00:00:00 | 100 |
| 2 | August, 19 2016 00:00:00 | August, 25 2016 00:00:00 | 120 |
| 3 | August, 26 2016 00:00:00 | September, 01 2016 00:00:00 | 80 |
Query 3:
select *
from offers
where (((arrival >= '2016-08-18' and arrival <= '2016-08-20') or
(depart >= '2016-08-18' and depart <= '2016-08-20'))
and datediff(depart,arrival) = 6)
or
(arrival = '2016-08-18' and depart = '2016-08-20'
and datediff(depart,arrival) = 2)
Results:
| ID | Arrival | Depart | Price |
|----|--------------------------|--------------------------|-------|
| 1 | August, 12 2016 00:00:00 | August, 18 2016 00:00:00 | 100 |
| 2 | August, 19 2016 00:00:00 | August, 25 2016 00:00:00 | 120 |
| 5 | August, 18 2016 00:00:00 | August, 20 2016 00:00:00 | 60 |

Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Sum

Data for every month is there till December. I want to get a new column as "sum" , which will sum till the previous month. Means, now it is august. So, the sum should be placed till july as shown in first entry in "Sum" column below.
Jan| Feb| Mar| Apr| May| Jun |Jul |Aug |Sum
21 | 28 | 26 | 31 | 54 | 67 |38 |29 |265
11 | 44 | 66 | 7 | 88 | 54 |90 |74 |
13 | 45 | 26 | 38 | 36 | 39 |67 |49 |
76 | 35 | 67 | 23 | 76 | 54 |35 |59 |
Since months are closed set, I prefer this simple solution:
SELECT *,
CASE WHEN DATEPART(MONTH, #date)<=1 THEN 0 ELSE Jan END +
CASE WHEN DATEPART(MONTH, #date)<=2 THEN 0 ELSE Feb END +
CASE WHEN DATEPART(MONTH, #date)<=3 THEN 0 ELSE Mar END +
CASE WHEN DATEPART(MONTH, #date)<=4 THEN 0 ELSE Apr END +
CASE WHEN DATEPART(MONTH, #date)<=5 THEN 0 ELSE May END +
CASE WHEN DATEPART(MONTH, #date)<=6 THEN 0 ELSE Jun END +
CASE WHEN DATEPART(MONTH, #date)<=7 THEN 0 ELSE Jul END +
CASE WHEN DATEPART(MONTH, #date)<=8 THEN 0 ELSE Aug END +
CASE WHEN DATEPART(MONTH, #date)<=9 THEN 0 ELSE Sep END Sum
--Append 3 more months to DECEMBER
FROM Months
SELECT
Jan,Feb,Mar,Apr, and so on...
,COALESCE(Jan,0) + COALESCE(Feb,0) + COALESCE(Mar,0) + COALESCE(Apr,0)..(till Dec) AS "Sum"
FROM MyTable
IF a row entrie is NULL the SUM will be NULL, therefor is the COALESCE-Function which will turn a NULL into ZERO
DECLARE #DATE date
SET #date = GETDATE()
SELECT LEFT(DATENAME(MM, #DATE), 3)

Order by month giving reverse order in Mysql

I'm trying to order a mysql query according to the month name but it is ordering in reverse order .I have tried both the ASC and DESC order but not working!This is what i'm getting :
order_amount month_number
370.245 Dec
0.01 Aug
0.02 July
0.01 May
2 Apr
3 Mar
4 Feb
5 Jan
This is the query:
select sum(amnt) as order_amount,month_number from orders where paid =1 GROUP BY month_number ORDER BY MONTH(month_number) ASC
This is a sample table 'order' on which i'm running query
order_amount month_number paid
370.245 Jan 1
0.01 Aug 1
0.02 July 1
0.01 Apr 1
2 May 0
3 Mar 1
4 Feb 0
5 Nov 0
month_number() is a lousy name for a string name for the month. That said, your attempts will not work, because month() takes a date, not a string representation. The best way is probably just to use case:
order by (case when month_number = 'Jan' then 1
when month_number = 'Feb' then 2
. . .
when month_number = 'Dec' then 12
end)
I would strongly suggest you include a date or actual month number in the table. And, rename month_number to something like month_name.
According to your sample, you've got 'Dec', 'Aug' and so on in your month_number field.
How is month(month_number) supposed to work if you don't pass a valid date type? if you pass some random text you'll just get NULL and can't order by NULL
http://sqlfiddle.com/#!2/3a774d/3/0
http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_month
SELECT * FROM my_table;
+-------+
| month |
+-------+
| Apr |
| Aug |
| Dec |
| Feb |
| Jan |
| Jul |
| Jun |
| Mar |
| May |
| Nov |
| Oct |
| Sep |
+-------+
SELECT * FROM my_table ORDER BY STR_TO_DATE(CONCAT('2014-',month,'-01'),'%Y-%b-%d');
+-------+
| month |
+-------+
| Jan |
| Feb |
| Mar |
| Apr |
| May |
| Jun |
| Jul |
| Aug |
| Sep |
| Oct |
| Nov |
| Dec |
+-------+
... or something like that