MySQL Query for calculating the days between 2dates group by months - mysql

I have 2days like check_in(25/04/2011), and a check_out(04/06/2011). Now I need to calculate the days between the dates in month wise.
ie. Month - Days,
April - 5,
May - 31,
June - 4,
Please help me in building as mysql query for getting the above result.
Thanks in advance.

If you want strictly done in MySQL, you need to create a Stored Procedure to do this.
Something like this in line of Stored Procedure (and range is not higher than a year).
DROP PROCEDURE IF EXISTS `getDateDiffBreakdown`;
CREATE PROCEDURE `getDateDiffBreakdown`(_DATE1 DATE,_DATE2 DATE)
BEGIN
IF (MONTH(_DATE1)<>MONTH(_DATE2)) THEN
-- we detected a month change
-- compute the selection based on current date and last day of month
SELECT CONCAT(DATE_FORMAT(_DATE1,'%M'),' - ',TO_DAYS(LAST_DAY(_DATE1))-TO_DAYS(_DATE1)+1);
-- step into next month and re-run the calc
call getDateDiffBreakdown(DATE_ADD(LAST_DAY(_DATE1),INTERVAL 1 DAY),_DATE2);
ELSE
-- same month, do the calculation
SELECT CONCAT(DATE_FORMAT(_DATE1,'%M'),' - ',TO_DAYS(_DATE2)-TO_DAYS(_DATE1)+1);
END IF;
END;
call like this:
set max_sp_recursion_depth = 11;
call getDateDiffBreakdown('2011-12-11','2012-06-03');
UPDATE
In another approach to get in 1 line, it would be:
DROP PROCEDURE IF EXISTS `getDateDiffBreakdown2`;
CREATE PROCEDURE `getDateDiffBreakdown2`(IN _DATE1 DATE,IN _DATE2 DATE, INOUT _RETURN VARCHAR(1000))
BEGIN
IF (MONTH(_DATE1)<>MONTH(_DATE2)) THEN
-- we detected a month change
-- compute the selection based on current date and last day of month
SET _RETURN=CONCAT(_RETURN,DATE_FORMAT(_DATE1,'%M'),' - ',TO_DAYS(LAST_DAY(_DATE1))-TO_DAYS(_DATE1)+1);
SET _RETURN = CONCAT(_RETURN,",");
-- step into next month and re-run the calc
call getDateDiffBreakdown2(DATE_ADD(LAST_DAY(_DATE1),INTERVAL 1 DAY),_DATE2,_RETURN);
ELSE
-- same month, do the calculation
SET _RETURN=CONCAT(_RETURN,DATE_FORMAT(_DATE1,'%M'),' - ',TO_DAYS(_DATE2)-TO_DAYS(_DATE1)+1);
END IF;
END;
call like this:
set max_sp_recursion_depth = 255;
set #TEMP = '';
call getDateDiffBreakdown2('2011-12-11','2012-06-03',#TEMP);
SELECT #TEMP;

I was trying to solve the problem too.
Pentium10 is too strong and now I'll try his solution. :)
By the way this is mine.
delimiter //
drop procedure if exists groupDaysByMonth//
create procedure groupDaysByMonth(in dStart date,in dEnd date)
begin
declare i int default 0;
declare months,days int;
drop table if exists t;
create temporary table t (
month_year varchar(50),
daysNum int
);
set months = (select period_diff(date_format(dEnd,'%Y%m'),date_format(dStart,'%Y%m')));
while i<=months do
if months = 0 then
set days = (select datediff(dEnd,dStart));
elseif i = 0 then
set days = ( select datediff(concat(date_format(dStart,'%Y-%m-'),day(last_day(dStart))),dStart));
elseif months = i then
set days = (select datediff(dEnd,date_format(dEnd,'%Y-%m-01'))+1);
else
set days = ( select day(last_day(dStart + interval i month)));
end if;
insert into t (month_year,daysNum) values(date_format(dStart + interval i month,'%M %Y'),days);
set i = i + 1;
end while;
select * from t;
end //
delimiter ;
mysql> call groupDaysByMonth('2011-04-25','2011-04-30');
+------------+---------+
| month_year | daysNum |
+------------+---------+
| April 2011 | 5 |
+------------+---------+
1 row in set (0.01 sec)
mysql> call groupDaysByMonth('2011-04-25','2011-06-04');
+------------+---------+
| month_year | daysNum |
+------------+---------+
| April 2011 | 5 |
| May 2011 | 31 |
| June 2011 | 4 |
+------------+---------+
3 rows in set (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
mysql> call groupDaysByMonth('2011-09-25','2012-05-02');
+----------------+---------+
| month_year | daysNum |
+----------------+---------+
| September 2011 | 5 |
| October 2011 | 31 |
| November 2011 | 30 |
| December 2011 | 31 |
| January 2012 | 31 |
| February 2012 | 29 |
| March 2012 | 31 |
| April 2012 | 30 |
| May 2012 | 2 |
+----------------+---------+
9 rows in set (0.01 sec)
Query OK, 0 rows affected (0.03 sec)
Hope that it helps.

Follow Answer 1 -- i am using it for multi year n the result set is month number with year and then the days of month format is "month number - last two digit of year - total days in month."
you can change the format of display according your need
**UPDATE**
In another approach to get in 1 line, it would be:
DROP PROCEDURE IF EXISTS `getDateDiffBreakdown2`;
CREATE PROCEDURE `getDateDiffBreakdown2`(IN _DATE1 DATE,IN _DATE2 DATE, INOUT _RETURN VARCHAR(1000))
BEGIN
IF (MONTH(_DATE1)<>MONTH(_DATE2)) THEN
-- we detected a month change
-- compute the selection based on current date and last day of month
SET _RETURN=CONCAT(_RETURN,DATE_FORMAT(_DATE1,'%M'),' - ',TO_DAYS(LAST_DAY(_DATE1))-TO_DAYS(_DATE1)+1);
SET _RETURN = CONCAT(_RETURN,",");
-- step into next month and re-run the calc
call getDateDiffBreakdown2(DATE_ADD(LAST_DAY(_DATE1),INTERVAL 1 DAY),_DATE2,_RETURN);
ELSE
-- same month, do the calculation
SET _RETURN=CONCAT(_RETURN,DATE_FORMAT(_DATE1,'%M'),' - ',TO_DAYS(_DATE2)-TO_DAYS(_DATE1)+1);
END IF;
END;
call like this:
set max_sp_recursion_depth = 255;
set #TEMP = '';
call getDateDiffBreakdown2('2011-12-11','2012-06-03',#TEMP);
SELECT #TEMP;
I have applied this post in one of my requirement but i found it buggy. i can be wrong if any one prove it and provide a better solution.
look how i am calling it and what i am getting :
set max_sp_recursion_depth = 255;
set #TEMP = '';
call getDateDiffBreakdown2('2010-12-10' , '2011-12-10',#TEMP);
SELECT #TEMP;
in result i get : '12 - 10 - 366'
year is changed but month is same.
i have tweaked the function as following: kindly let me know if some thing is strange. thanks
DROP PROCEDURE IF EXISTS `getDateDiffBreakdown2`;
CREATE PROCEDURE `getDateDiffBreakdown2`(IN _DATE1 DATE,IN _DATE2 DATE, INOUT _RETURN VARCHAR(1000))
BEGIN
IF(YEAR(_DATE1)<>YEAR(_DATE2)) THEN
SET _RETURN=CONCAT(_RETURN,DATE_FORMAT(_DATE1,'%b - %y'),' - ',TO_DAYS(LAST_DAY(_DATE1))-TO_DAYS(_DATE1)+1);
SET _RETURN = CONCAT(_RETURN,",");
-- step into next month and re-run the calc
call getDateDiffBreakdown2(DATE_ADD(LAST_DAY(_DATE1),INTERVAL 1 DAY),_DATE2,_RETURN);
ELSEIF (MONTH(_DATE1)<>MONTH(_DATE2)) THEN
-- we detected a month change
-- compute the selection based on current date and last day of month
SET _RETURN=CONCAT(_RETURN,DATE_FORMAT(_DATE1,'%M'),' - ',TO_DAYS(LAST_DAY(_DATE1))-TO_DAYS(_DATE1)+1);
SET _RETURN = CONCAT(_RETURN,",");
-- step into next month and re-run the calc
call getDateDiffBreakdown2(DATE_ADD(LAST_DAY(_DATE1),INTERVAL 1 DAY),_DATE2,_RETURN);
ELSE
-- same month, do the calculation
SET _RETURN=CONCAT(_RETURN,DATE_FORMAT(_DATE1,'%M'),' - ',TO_DAYS(_DATE2)-TO_DAYS(_DATE1)+1);
END IF;
END;

Use TO_DAYS(date) function for that.

Related

MYSQL - Calculating Friday as "end of week" is returning "null" when "start of week" is in April and "end of week" is May, how do I fix this?

I'm trying to get the last working day of the week (Friday) e.g. 2019-04-30 should give me 2019-05-04, but instead returns Null. How would I solve this? My existing code is below.
DELIMITER ;;
CREATE FUNCTION FIRST_DAY_OF_WEEK(day DATE)
RETURNS DATE DETERMINISTIC
BEGIN
RETURN SUBDATE(day, WEEKDAY(day));
END;;
DELIMITER ;
SELECT ID, parent_id, created_by_id, old_value, new_value, field, date, date(FIRST_DAY_OF_WEEK(date)+4) AS end_of_week_fri
FROM new_history_team_member;
Use date_Add
set #date = '2019-04-30';
select f(#date),date_add(F(#date), interval 4 day);
+------------+------------------------------------+
| f(#date) | date_add(F(#date), interval 4 day) |
+------------+------------------------------------+
| 2019-04-29 | 2019-05-03 |
+------------+------------------------------------+
1 row in set (0.024 sec)
PS I renamed your function to F so as not to clutter up my db but otherwise unchanged

GREATEST() & LEAST() functions not working with TIME variables in stored procedure in MySQL 5.7

I have a MySQL stored procedure calculating employees' working time, overtime etc. On some special case I needed to calculate the overlapping between two periods (the employee's working hours and the company working hours) and I did it using the GREATEST() and LEAST() functions on variables of type TIME.
The stored procedure produced the correct results in MySQL 5.5 but changed behavior when I upgraded to MySQL 5.7. Actually, it works in MySQL 5.7 if I change the variable type to VARCHAR() but the data it works on comes from table fields of type TIME.
Below is a simplified form of the procedure demonstrating the problem.
DROP procedure IF EXISTS `timetest`;
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `timetest`()
BEGIN
DECLARE working_from, working_to, open_from, open_to TIME;
-- This data actually comes from table column of type TIME
SET working_from = '08:00:00';
SET working_to = '16:00:00';
SET open_from = '07:00:00';
SET open_to = '14:00:00';
IF open_from IS NOT NULL AND open_to IS NOT NULL THEN
SET working_from = GREATEST(working_from, open_from);
SET working_to = LEAST(working_to, open_to);
END IF;
SELECT working_from, working_to;
END$$
DELIMITER ;
In MySQL 5.5:
mysql> call timetest();
+--------------+------------+
| working_from | working_to |
+--------------+------------+
| 08:00:00 | 14:00:00 |
+--------------+------------+
1 row in set (0.00 sec)
In MySQL 5.7:
mysql> call timetest();
+--------------+------------+
| working_from | working_to |
+--------------+------------+
| 07:00:00 | 14:00:00 |
+--------------+------------+
1 row in set (0,00 sec)
Looks like the GREATEST doesn't detect the values as TIME values. You can solve this issue by using CAST(column_name AS TIME):
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `timetest`()
BEGIN
DECLARE working_from, working_to, open_from, open_to TIME;
-- This data actually comes from table column of type TIME
SET working_from = '08:00:00';
SET working_to = '16:00:00';
SET open_from = '07:00:00';
SET open_to = '14:00:00';
IF open_from IS NOT NULL AND open_to IS NOT NULL THEN
SET working_from = CAST(GREATEST(working_from, open_from) AS TIME);
SET working_to = CAST(LEAST(working_to, open_to) AS TIME);
END IF;
SELECT working_from, working_to;
END$$
DELIMITER ;
demo on dbfiddle.uk
This looks like a bug on MySQL 5.7 (see this bug report):
CREATE PROCEDURE `test_greatest`()
BEGIN
DECLARE time_1 TIME;
DECLARE time_2 TIME;
DECLARE time_3 TIME;
DECLARE result TIME;
SET time_1 = '08:00:00';
SET time_2 = '07:00:00';
SET time_3 = '06:00:00';
-- always returns the last parameter.
SET result = GREATEST(time_1, time_2, time_3); -- result is 06:00:00
SET result = GREATEST(time_1, time_3, time_2); -- result is 07:00:00
SET result = GREATEST(time_2, time_3, time_1); -- result is 08:00:00
-- using a CAST on first parameter the GREATEST is working.
SET result = GREATEST(CAST(time_1 AS TIME), time_2, time_3); -- result is 08:00:00
SET result = GREATEST(time_1, CAST(time_2 AS TIME), time_3); -- result is 06:00:00
SET result = GREATEST(time_1, time_2, CAST(time_3 AS TIME)); -- result is 06:00:00
-- using CAST on the whole GREATEST is working.
SET result = CAST(GREATEST(time_1, time_2, time_3) AS TIME); -- result is 08:00:00
SELECT result;
END
This example always returns the last parameter (time_3) of GREATEST.

how to splite two dates between week in mysql month wise

I want to display number of row in MySQL according number of week.
Suppose we pass jan startdate 2017-1-1 and endDate 2017-1-31 then totdal days is 31. We have to divide by 7 then it will 4 week and 3 days so total 5 week so I have to display like this
for jan
WeekRow
1
2
3
4
5
for Feb
WeekRow
1
2
3
4
I am trying to apply a query, but I am unable to do it.
I am facing this issue from long time, but still unable to fix.
below code is working to get rid your problem.
i have wrote this code in hurry .....later on i ll optimize this ...
have a look..and let me know if you got any problem to understand it...
CREATE DEFINER=`root`#`localhost` PROCEDURE `sp_month_days_split`(in in_start_date datetime , in in_end_date datetime)
BEGIN
declare temp_date datetime;
declare temp_date_data int ;
drop temporary table if exists temp_split;
create temporary table temp_split
(
number bigint not null
);
while(in_start_date <= in_end_date)
do
set temp_date = (select date_add(in_start_date,interval 7 day));
set temp_date_data = (select day(date_add(in_start_date,interval 6 day)));
set #temp_last_number = (select number from temp_split order by number desc limit 1);
if(temp_date_data % 7) =0
Then
insert into temp_split(number)
select (temp_date_data/7);
else
insert into temp_split(number)
select #temp_last_number+1;
end if ;
set in_start_date = temp_date ;
end while;
select * from temp_split;
END
call
call sp_month_days_split('2017-12-01', '2017-12-31');

Launch Trigger and Routine on Insert

Im attempting to have MySQL automatically insert data into another table after insert into one. I know to do this required Triggers and potentially Routines. I have a couple I've been trying to modify to do what I wish to accomplish but I appear to be hitting a dead end due to lack of experience, so help is greatly appreciated.
The table that has data inserted (db_tou_tracking):
tou_tracking_ID ICP_ID tou_tracking_start tou_tracking_units
----------------------------------------------------------------
2 2 2013-03-01 10.77
3 2 2013-03-01 11.00
There are a couple of other columns here, that separate out by time, but I'm interested by day, rather than time.
Table data should go into compounded. So as each of the above rows are inserted, it will either create a new row if the tou_tracking_start and ICP_ID do not exist, or update the existing row.
tou_tracking_daily_ID ICP_ID tou_tracking_start tou_tracking_units
------------------------------------------------------------------------------
1 2 2013-03-01 21.77
2 2 2013-03-02 25.36
Below is my Tigger (no errors when setup on MySQL, and it does appear to call when data is attempted to be inserted):
BEGIN
DECLARE presentcount INT;
SET presentcount = (SELECT count(*) FROM db_tou_tracking_daily WHERE tou_tracking_daily_day =
(SELECT tou_tracking_start FROM db_tou_tracking WHERE ICP_ID = db_tou_tracking_daily.ICP_ID ORDER BY tou_tracking_ID DESC)
);
IF (presentcount = 0) THEN
INSERT INTO db_tou_tracking_daily (ICP_ID, tou_tracking_daily_day, tou_tracking_start)
SELECT NEW.ICP_ID, NEW.tou_tracking_start, NEW.tou_tracking_units, calculate_units(NEW.ICP_ID, NEW.tou_tracking_start);
ELSE
UPDATE db_tou_tracking_daily SET tou_tracking_daily_units = calculate_units(NEW.ICP_ID, tou_tracking_daily_day)
WHERE ICP_ID = NEW.ICP_ID AND tou_tracking_daily_day = NEW.tou_tracking_start;
END IF;
END
and then the routine it calls to calculate units.
CREATE DEFINER=`root`#`localhost` FUNCTION `calculate_units`(ICP_ID INT, tou_tracking_daily_day DATE) RETURNS float
BEGIN
DECLARE units FLOAT;
DECLARE last_time DATE;
DECLARE last_watts INT;
DECLARE this_time DATETIME;
DECLARE this_watts INT;
DECLARE loop_done INT;
DECLARE curs CURSOR FOR
SELECT tou_tracking_timestart, tou_tracking_units FROM db_tou_tracking WHERE ICP_ID = ICP_ID AND tou_tracking_start = tou_tracking_daily_day ORDER BY tou_tracking_start DESC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET loop_done = 1;
SET last_time = (SELECT max(tou_tracking_start) FROM db_tou_tracking WHERE ICP_ID = ICP_ID AND tou_tracking_start < tou_tracking_daily_day);
SET last_watts = (SELECT tou_tracking_units FROM db_tou_tracking WHERE ICP_ID = ICP_ID AND tou_tracking_start = last_time);
SET last_time = CAST(tou_tracking_start AS DATETIME);
SET loop_done = 0;
SET units = 0;
OPEN curs;
REPEAT
FETCH curs INTO this_time, this_watts;
IF last_watts IS NOT NULL THEN
SET units = units + (last_watts + this_watts);
END IF;
SET last_watts = this_watts;
SET last_time = this_time;
UNTIL loop_done END REPEAT;
CLOSE curs;
END
The routine throws back an error on line 3 when I try to run the SQL to setup the routine, but I can't see anything obviously wrong, but I'm not exactly sure what I'd be looking for.
Any help with this is hugely appreciated and any pointers that can be given along the way. Thanks :)
Attempting to replicate your issue, I'm going to guess the error you get is probably because you're not using a DELIMITER.
Executing a similar function creation statement I get the same error, and a syntax parse suggests it's not expecting the delimiter ;.
The one that causes an error on line 3.
CREATE DEFINER = 'root'#'localhost' FUNCTION test_func(foo INT) RETURNS FLOAT
BEGIN
DECLARE bar FLOAT;
RETURN 1;
END
Fixing it using delimiters.
DELIMITER $$
CREATE DEFINER = 'root'#'localhost' FUNCTION test_func(foo INT) RETURNS FLOAT
BEGIN
DECLARE bar FLOAT;
RETURN 1;
END$$
DELIMITER ;
If this does not fix your problem, are you able to provide a self contained function that doesn't rely on any of your existing tables, that also produces the same error so it can be tested?
create table t1 ( start date not null, units decimal(5,2) not null );
create table t2 ( start date not null, units decimal(5,2) not null );
delimiter //
create trigger trg1
after insert on t1
for each row
begin
update t2
set units = units + new.units
where start = new.start;
if ROW_COUNT() = 0 then
insert into t2
(start, units)
values (new.start, new.units);
end if;
end //
delimiter ; //
mysql> select * from t1;
Empty set (0.01 sec)
mysql> select * from t2;
Empty set (0.00 sec)
mysql> insert into t1 (start, units) values ('2014-01-01',100.02);
Query OK, 1 row affected (0.01 sec)
mysql> select * from t1;
+------------+--------+
| start | units |
+------------+--------+
| 2014-01-01 | 100.02 |
+------------+--------+
1 row in set (0.00 sec)
mysql> select * from t2;
+------------+--------+
| start | units |
+------------+--------+
| 2014-01-01 | 100.02 |
+------------+--------+
1 row in set (0.00 sec)
mysql> insert into t1 (start, units) values ('2014-01-01',200.05);
Query OK, 1 row affected (0.01 sec)
mysql> select * from t1;
+------------+--------+
| start | units |
+------------+--------+
| 2014-01-01 | 100.02 |
| 2014-01-01 | 200.05 |
+------------+--------+
2 rows in set (0.01 sec)
mysql> select * from t2;
+------------+--------+
| start | units |
+------------+--------+
| 2014-01-01 | 300.07 |
+------------+--------+
1 row in set (0.01 sec)

MySQL add timestamp values

I have a table in MySQL with this format: (time = timestamp on insert)
id | tid | uid | time
31 | 1 | 14 | 2011-05-19 05:42:37 //start timestamp)
41 | 1 | 14 | 2011-05-19 07:18:42 //stop timestamp)
45 | 1 | 14 | 2011-05-19 07:18:49 //start timestamp)
46 | 1 | 14 | 2011-05-19 07:28:42 //stop timestamp)
What I need is to make a select that adds the time differences like this
(41 - 31) + (46 - 45) (i'm using the id's instead of the actual time values to better understand what I need to do )
something like SELECT (something that does this) AS TotalTimeSpent WHERE tid = '1'
If you insist on using this table layout (and I really hope you change your mind, it is truly horrific), you can do it with cursors in a stored procedure.
Pseudo-code would be something like this:
CREATE PROCEDURE gettotaltime()
BEGIN
DECLARE total, curr, prev DATETIME;
DECLARE odd INT DEFAULT 1;
DECLARE done INT DEFAULT 0;
DECLARE c CURSOR FOR SELECT time FROM tbl;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN c;
read_loop: LOOP
FETCH c INTO curr;
IF odd=0 THEN
SET total=dateadd(total,datediff(curr,prev)); -- or something similar, I forget
END IF;
SET prev=curr;
SET odd=1-odd;
IF done THEN
LEAVE read_loop;
END IF;
END LOOP;
CLOSE c;
SELECT total;
END;