What is the MySQL alternative to Oracle's NEXT_DAY function? - mysql

NEXT_DAY("01-SEP-95","FRIDAY") returns what is the date on next Friday, but in MySQL this function does not seem to be appear. What is the alternative?

I'm going to throw my hat in the ring with yet another approach:
Edit: I realize somewhat belatedly that the Oracle function in question takes a string as the second argument, and so this doesn't precisely fit the requirement. However MySQL has already kindly defined 0 - 6 as Monday - Sunday, and anyway I have moral objections to using a string as an argument for this type of thing. A string would either come from user input or yet another mapping in higher level code between numeric and string values. Why not pass an integer? :)
CREATE FUNCTION `fnDayOfWeekGetNext`(
p_date DATE,
p_weekday TINYINT(3)
) RETURNS date
BEGIN
RETURN DATE_ADD(p_date, INTERVAL p_weekday - WEEKDAY(p_date) + (ROUND(WEEKDAY(p_date) / (p_weekday + WEEKDAY(p_date) + 1)) * 7) DAY);
END
To break the portion down that determines the INTERVAL value:
The first part of the equation simply gets the offset between the weekday specified and the weekday of the date specified:
p_weekday - WEEKDAY(p_date)
This will return a positive number if p_weekday is greater than WEEKDAY(p_date) and vice versa. Zero will be returned if they're the same.
The ROUND() segment is used to determine whether the requested day of the week (p_weekday) has already occurred in the current week relative to the date (p_date) specified. So, by example...
ROUND(WEEKDAY('2019-01-25') / (6 + WEEKDAY('2019-01-25') + 1))
..returns 0, indicating that Sunday (6) has not occurred this week, as 2019-01-25 is a Friday. Likewise...
ROUND(WEEKDAY('2019-01-25') / (2 + WEEKDAY('2019-01-25') + 1))
...returns 1 because Wednesday (2) has already passed. Note that this will return 0 if p_weekday is the same as the weekday of p_date.
This value (either 1 or 0) is then multiplied by the constant 7 (the number of days in a week).
Hence if p_weekday has already occurred in the current week, it will add 7 to the offset p_weekday - WEEKDAY(p_date), because that offset would be a negative number and we want a date in the future.
If p_weekday has yet to occur in the current week, then we can just add the offset to the current date because the offset will be a positive number. Hence the section ROUND(...) * 7 is equal to zero and, in essence, ignored.
My desire for this approach was to simulate an IF() condition mathematically. This would be equally valid:
RETURN DATE_ADD(p_date, INTERVAL p_weekday - WEEKDAY(p_date) + IF(p_weekday - WEEKDAY(p_date) < 0, 7, 0) DAY);
And in the interest of objectivity, in running 1M iterations a few times of each function the IF-based version averaged about 4.2% faster than the ROUND-based version.

In MySQL, you can create user defined function
DELIMITER //
 
CREATE FUNCTION next_day(start_date DATETIME, weekday CHAR(20))
RETURNS DATETIME
BEGIN
DECLARE start DATETIME;
DECLARE i INT;
 
// Select the next date
SET start = ADDDATE(start_date, 1);
SET i = 1;
 
days: LOOP
-- Compare the day names
IF SUBSTR(DAYNAME(start), 1, 3) = SUBSTR(weekday, 1, 3) THEN
LEAVE days;
END IF;
 
// Select the next date
SET start = ADDDATE(start, 1);
SET i = i + 1;
 
-- Not valid weekday specified
IF i > 7 THEN
SET start = NULL;
LEAVE days;
END IF;
 
END LOOP days;
 
RETURN start;
END;
//
 
DELIMITER ;
and call it
SELECT NEXT_DAY("1995-09-01","FRIDAY")
Source : http://www.sqlines.com/mysql/how-to/next_day

Here's an answer to a different problem:
Find the date for Friday of the current week:-
SELECT STR_TO_DATE(CONCAT('2017',(SELECT DATE_FORMAT(CURDATE(),'%U')),' Friday'),'%Y %U %W') x;
+------------+
| x |
+------------+
| 2017-05-12 |
+------------+
The above hasn't been verified for robustness, but is provided 'as is' merely as food for thought.

SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE your_table ( value DATE );
INSERT INTO your_table ( value )
SELECT DATE '2017-05-08' FROM DUAL UNION ALL
SELECT DATE '2017-05-09' FROM DUAL UNION ALL
SELECT DATE '2017-05-10' FROM DUAL UNION ALL
SELECT DATE '2017-05-11' FROM DUAL UNION ALL
SELECT DATE '2017-05-12' FROM DUAL UNION ALL
SELECT DATE '2017-05-13' FROM DUAL UNION ALL
SELECT DATE '2017-05-14' FROM DUAL;
Query 1:
SELECT value,
ADDDATE(
value,
6 - DAYOFWEEK( value )
+ CASE WHEN DAYOFWEEK( value ) < 6 THEN 0 ELSE 7 END
) AS next_friday
FROM your_table
Results:
| value | next_friday |
|-----------------------|-----------------------|
| May, 08 2017 00:00:00 | May, 12 2017 00:00:00 |
| May, 09 2017 00:00:00 | May, 12 2017 00:00:00 |
| May, 10 2017 00:00:00 | May, 12 2017 00:00:00 |
| May, 11 2017 00:00:00 | May, 12 2017 00:00:00 |
| May, 12 2017 00:00:00 | May, 19 2017 00:00:00 |
| May, 13 2017 00:00:00 | May, 19 2017 00:00:00 |
| May, 14 2017 00:00:00 | May, 19 2017 00:00:00 |

Short Version:
Utilizing DAYOFWEEK() you can leverage the number values to create a closed formula that tells you how many days to add. You can then utilize DATE_ADD to help you shift the date. There may be a cleaner way, however you should be able to make a function out of this.
The following is an Excel formula, which you use to verify all 49 use cases: =MOD(MOD(Next-Input, 7)+7,7)
Some examples of how the excel calculations can be translated into SQL:
SELECT (((1-DAYOFWEEK('2018-08-15')) % 7)+7) % 7 AS DaysToAddToGetNextSundayFromWednesday
FROM dual; -- 4
SELECT DATE_ADD('2018-08-15', INTERVAL (((1-DAYOFWEEK('2018-08-15')) % 7)+7) % 7 DAY) AS NextSundayFromDate
FROM dual; -- 2018-08-19
If you plan to use the above often, you'll probably want to make a function or stored procedure.
Long Version:
I've run into this problem multiple times over the years. First time I created a giant case statement for all 49 cases. I found that this made the queries super big and not clean. I wanted a cleaner, simpler solution like a closed in formula. Below are details to to calculations I did to confirm the formula above works. I tested this with MySQL 5.7 if you are using MySQL 8.5 it looks like the function is built in ( Reference: http://www.sqlines.com/mysql/how-to/next_day ).
Note: Excel doesn't return negative results for modulus, where MySQL does. That is why we add 7 and do another modulus.
DAYOFWEEK()
Sun 1
Mon 2
Tues 3
Wed 4
Thur 5
Fri 6
Sat 7
Input Next Expected Days to Add Formula Result
1 1 0 0
1 2 1 1
1 3 2 2
1 4 3 3
1 5 4 4
1 6 5 5
1 7 6 6
2 1 6 6
2 2 0 0
2 3 1 1
2 4 2 2
2 5 3 3
2 6 4 4
2 7 5 5
3 1 5 5
3 2 6 6
3 3 0 0
3 4 1 1
3 5 2 2
3 6 3 3
3 7 4 4
4 1 4 4
4 2 5 5
4 3 6 6
4 4 0 0
4 5 1 1
4 6 2 2
4 7 3 3
5 1 3 3
5 2 4 4
5 3 5 5
5 4 6 6
5 5 0 0
5 6 1 1
5 7 2 2
6 1 2 2
6 2 3 3
6 3 4 4
6 4 5 5
6 5 6 6
6 6 0 0
6 7 1 1
7 1 1 1
7 2 2 2
7 3 3 3
7 4 4 4
7 5 5 5
7 6 6 6
7 7 0 0

Related

SQL : Increment a new column whenever there is a multiple of 5 else dont

I came across this question in a round of interview. A table has the following column.
ID
1
2
3
4
5
6
7
8
9
11
12
13
14
15
16
17
18
19
20
22
23
24
26
The question is to create a new column that starts with '1' and increments on the next ID whenever there is a multiple of 5. So the expected output is
ID
Result
1
1
2
1
3
1
4
1
5
1
6
2
7
2
8
2
9
2
11
2
12
2
13
2
14
2
15
2
16
3
17
3
18
3
19
3
20
3
22
4
23
4
24
4
26
4
You can combine two window functions: LAG() and SUM(). For example:
select id,
1 + sum(case when lid % 5 = 0 then 1 else 0 end) over(order by id) as v
from (
select *, lag(id) over(order by id) as lid from t
) x
order by id
Result:
id v
-- -
1 1
2 1
3 1
4 1
5 1
6 2
7 2
8 2
9 2
11 2
12 2
13 2
14 2
15 2
16 3
17 3
18 3
19 3
20 3
22 4
23 4
24 4
26 4
See running example at DB Fiddle.
For MySQL 5+ you may use, for example
SELECT id, (#result := COALESCE( #result + !(id % 5), 1 )) - !(id % 5) result
FROM t
CROSS JOIN (SELECT #result := NULL) init_variable
ORDER BY id
For MySQL 8+ use
SELECT id, 1 + SUM(!(id % 5)) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) resuls
FROM t
You can do this without a subquery:
select t.*,
1 + sum( (id % 5) = 0 ) over (order by id) - (id % 5 = 0)
from t
order by id;
What is the logic here? Calculate the cumulative sum of "5"s up to this row. Then subtract out the value on this row, because the increment takes effect on the next row.
It is also tempting to write this using a window frame clause, but that ends up being a wee bit more complicated because the first value is NULL:
select t.*,
1 + coalesce(sum( (id % 5) = 0 ) over (order by id rows between unbounded preceding and 1 preceding), 0)
from t
order by id;
Here is a db<>fiddle.

how to find overlapping record with between date

I am using MySQL, I have following table structure
Id id2 classId sectionId validFrom validTill
------------------------------------------------------
1 1 5 13 2016-01-01 2016-03-30
2 1 5 22 2016-01-15 2016-03-30
3 1 5 23 2016-01-15 2016-04-29
4 1 5 13 2016-04-01 2016-04-30
9 10 6 24 2016-01-17 2016-02-05
10 10 6 25 2016-01-23 2016-02-05
11 10 6 24 2016-01-31 2016-02-05
My SQL statement is
SELECT count(*) as timeCount FROM TimeTableClassSection a
WHERE classId=5 AND sectionId=13 AND ((a.ValidFrom BETWEEN '2016-01-18' AND '2016-01-24') OR (a.ValidTill BETWEEN '2016-01-18' AND '2016-01-24'))
Its returning timeCount = 0. But it should return 1 as record with Id=1 falls between this date range ('2016-01-18' AND '2016-01-24')
I am trying to achieve, find out any overlapping record for particular classId & sectionId between provided date range.
If classId=5 and sectionId=13 has validFrom=2016-01-01 validTill=2016-03-30 exist, then any date range between this date range ('2016-01-18' AND '2016-01-24') should throw this record as count.
If I give date range 2015-12-25 to 2016-09-20 then record count should = 1
If I give date range 2016-2-1 to 2016-02-20 then record count should = 1
If I give date range 2016-2-1 to 2016-09-20 then record count should = 1
What wrong I am doing here ... all date format is in YYYY-MM-DD
You are only checking if the boundaries are within the date range, but you do not check whether the data range is within the boundaries. You should extend your where criteria:
...AND ((a.ValidFrom BETWEEN '2016-01-18' AND '2016-01-24')
OR (a.ValidTill BETWEEN '2016-01-18' AND '2016-01-24')
OR (a.ValidFrom<'2016-01-18' AND a.ValidTill>'2016-01-24'))

schedule after 0:00 hour not working before first interval

I have a table with 4 schedule interval:
id time_int progr_comment A B C D
1 05:30:00 Good Morning 1 4 2 17
2 06:50:00 Have a nice day 1 4 2 17
3 17:00:00 Welcome Home 1 4 4 23
4 18:30:00 Good Evening 1 4 2 22
5 22:00:00 Good Night 1 4 2 20
For each interval I compare with NOW() and I will take the variables (A, B, C, D) from table based on time_int <= NOW() then my program create a query to mysql. The problem come after 0:00 hour. The query does not find anything until NOW() pass the first interval (ex. 5:30:00).

How to get this coming Sunday's date?

I need to get "this coming Sunday"'s date
How could I do this?
I've seen the DAYOFWEEK function but that's not really what I'm after (might be useful in the WHERE clause)
EDIT:
I changed question back from my change to Saturday to the original Sunday since I got some valid answers for Sunday and it may help some people in the future
I found a number of other 'end of week' date questions and answers elsewhere (including here on SO)
The most useful solution for me was the accepted answer found here.
UPDATE
Better use curdate() instead of now(), so the final conversion to DATE type via date() can be omitted.
SELECT curdate() + INTERVAL 6 - weekday(curdate()) DAY;
for next sunday or
SET #date = '2014-03-05';
SELECT #date + INTERVAL 6 - weekday(#date) DAY;
for a variable date.
ORIGINAL ANSWER
Simply use this statement for the next sunday:
SELECT date(now() + INTERVAL 6 - weekday(now()) DAY);
Output
+-----------------------------------------------+
| date(now() + INTERVAL 6 - weekday(now()) DAY) |
+-----------------------------------------------+
| 2014-04-13 |
+-----------------------------------------------+
Explanation:
weekday(now()) returns the current weekday (starting with 0 for monday, 6 is sunday). Subtract the current weekday from 6 and get the remaining days until next sunday as a result. Then add them to the current date and get next sunday's date.
Or if you want to keep it flexible to work with any date:
SET #date = '2014-03-05';
SELECT date(#date + INTERVAL 6 - weekday(#date) DAY);
Output
+-----------------------------------------------+
| date(#date + INTERVAL 6 - weekday(#date) DAY) |
+-----------------------------------------------+
| 2014-03-09 |
+-----------------------------------------------+
To find the next date for given week day use this query. set the value of the week day you want in the THEDAY variable.
SET #THEDAY = 1;
SELECT DATE_ADD(NOW(), INTERVAL if( #THEDAY > WEEKDAY(NOW()) , (#THEDAY - WEEKDAY(NOW())) , (7 - WEEKDAY(NOW())) + #THEDAY) DAY)
Note that the weekday index starts from 0 For Monday ...... 6 For Sunday
Date()+8-WeekDay(Date())
and try this
=IIf(Weekday(Now())=1,DateAdd("d",7,Now()),
IIf(Weekday(Now())=2,DateAdd("d",6,Now()),
IIf(Weekday(Now())=3,DateAdd("d",5,Now()),
IIf(Weekday(Now())=4,DateAdd("d",4,Now()),
IIf(Weekday(Now())=5,DateAdd("d",3,Now()),
IIf(Weekday(Now())=6,DateAdd("d",2,Now()),
IIf(Weekday(Now())=6,DateAdd("d",1,Now()) )))))))
the WEEK function can be paired with STR_TO_DATE for a nice one-liner:
SELECT STR_TO_DATE(CONCAT(YEAR(NOW()),WEEK(NOW(),1), ' Sunday'), '%X%V %W') as `this Sunday`;
+-------------+
| this Sunday |
+-------------+
| 2014-04-13 |
+-------------+;
If you follow the doc links, the overall strategy is mostly gleaned from the note on STR_TO_DATE about year/week conversion. Then the mode argument to WEEK is used to treat Sunday as the last instead of first day of the week. Essentially, we construct a string like this:
'201415 Sunday'
Which is to be interpreted as "in 2014, Sunday of the 15th week".
Just in case, I tested this at the year boundary... looks okay so far!
SELECT STR_TO_DATE(CONCAT(YEAR('2014-12-31'),WEEK('2014-12-31',1), ' Sunday'), '%X%V %W') as `this Sunday`;
+-------------+
| this Sunday |
+-------------+
| 2015-01-04 |
+-------------+
I posted a similar answer in What is the MySQL alternative to Oracle's NEXT_DAY function? which a more general solution to the question being asked here. The solution below will help you get to any specified next day (Sunday, Monday, Tuesday, Wednesday, Thursday, Friday or Saturday) similar to the NEXT_DAY() function in oracle.
Short Version:
Utilizing DAYOFWEEK() you can leverage the number values to create a closed formula that tells you how many days to add. You can then utilize DATE_ADD to help you shift the date. There may be a cleaner way, however you should be able to make a function out of this.
The following is an Excel formula, which you use to verify all 49 use cases: =MOD(MOD(Next-Input, 7)+7,7)
Some examples of how the excel calculations can be translated into SQL:
SELECT (((1-DAYOFWEEK('2018-08-15')) % 7)+7) % 7 AS DaysToAddToGetNextSundayFromWednesday
FROM dual; -- 4
SELECT DATE_ADD('2018-08-15', INTERVAL (((1-DAYOFWEEK('2018-08-15')) % 7)+7) % 7 DAY) AS NextSundayFromDate
FROM dual; -- 2018-08-19
If you plan to use the above often, you'll probably want to make a function or stored procedure.
Long Version:
I've run into this problem multiple times over the years. First time I created a giant case statement for all 49 cases. I found that this made the queries super big and not clean. I wanted a cleaner, simpler solution like a closed in formula. Below are details to to calculations I did to confirm the formula above works. I tested this with MySQL 5.7 if you are using MySQL 8.5 it looks like the function is built in ( Reference: http://www.sqlines.com/mysql/how-to/next_day ).
Note: Excel doesn't return negative results for modulus, where MySQL does. That is why we add 7 and do another modulus.
DAYOFWEEK()
Sun 1
Mon 2
Tues 3
Wed 4
Thur 5
Fri 6
Sat 7
Input Next Expected Days to Add Formula Result
1 1 0 0
1 2 1 1
1 3 2 2
1 4 3 3
1 5 4 4
1 6 5 5
1 7 6 6
2 1 6 6
2 2 0 0
2 3 1 1
2 4 2 2
2 5 3 3
2 6 4 4
2 7 5 5
3 1 5 5
3 2 6 6
3 3 0 0
3 4 1 1
3 5 2 2
3 6 3 3
3 7 4 4
4 1 4 4
4 2 5 5
4 3 6 6
4 4 0 0
4 5 1 1
4 6 2 2
4 7 3 3
5 1 3 3
5 2 4 4
5 3 5 5
5 4 6 6
5 5 0 0
5 6 1 1
5 7 2 2
6 1 2 2
6 2 3 3
6 3 4 4
6 4 5 5
6 5 6 6
6 6 0 0
6 7 1 1
7 1 1 1
7 2 2 2
7 3 3 3
7 4 4 4
7 5 5 5
7 6 6 6
7 7 0 0
The accepted answer will return previous Saturday on Sunday.
If you need the next Saturday for Sunday use this
SELECT
CAST(
Case When weekday(now()) = 6 Then (now() + INTERVAL weekday(now()) DAY)
else (now() + INTERVAL 5 - weekday(now()) DAY)
end
As Date) as saturday

Sum of overlap durations for each combination of 2 foreign keys in MySQL

i have a database with workers, stations and session. A session describes at which time which worker has been on which station. I managed to build a query that gives me the duration of the overlap of each session.
SELECT
sA.station_id,
sA.worker_id AS worker1,
sB.worker_id AS worker2,
SEC_TO_TIME(
TIME_TO_SEC(LEAST(sA.end,sB.end)) - TIME_TO_SEC(GREATEST(sA.start,sB.start))
) AS overlap
FROM
`sessions` AS sA,
`sessions` AS sB
WHERE
sA.station_id = sb.station_id
AND
sA.station_id = 6
AND (
sA.start BETWEEN sB.start AND sB.end
OR
sA.end BETWEEN sB.start AND sB.end
)
With this query i get an result like this
station_id worker1 worker2 overlap
6 1 1 09:00:00
6 2 1 02:30:00
6 5 1 00:00:00
6 1 1 09:00:00
6 2 1 01:30:00
6 3 1 09:00:00
...
6 12 3 02:00:00
6 14 3 01:00:00
6 17 3 02:00:00
...
What i would like now is to sum up the overlap for every combination of worker1 and worker2 to get the overall overlap duration.
I tried different ways of using SUM() and GROUP BY but i never got the wanted result.
SELECT
...
SEC_TO_TIME(
**SUM**(TIME_TO_SEC(LEAST(sA.end,sB.end)) - TIME_TO_SEC(GREATEST(sA.start,sB.start)))
) AS overlap
...
#has as result
station_id worker1 worker2 overlap
6 1 1 838:59:59
#in combination with
GROUP BY
worker1
#i get
station_id worker1 worker2 overlap
6 1 1 532:30:00
6 2 1 -33:00:00
6 3 1 270:30:00
6 5 1 598:30:00
6 6 1 542:00:00
6 7 1 508:00:00
6 8 5 53:00:00
6 9 1 54:30:00
6 10 1 310:00:00
6 11 1 -108:00:00
6 12 1 593:30:00
6 14 1 97:30:00
6 15 1 -53:30:00
6 17 1 293:30:00
the last result is close but i am still missing a lot of combinations. I also dont understand why the combination 8 - 5 is displayed.
thanks for ur help (and time to read)
aaargh, sorry for my stupidity, the solution was fairly simple
....
SUM(((UNIX_TIMESTAMP(LEAST(sA.end,sB.end))-UNIX_TIMESTAMP(GREATEST(sA.start,sB.start)))/3600))
...
GROUP BY station_id, worker1, worker2
ORDER BY worker1, worker2
i switched to using timestamps and transforming it to hours by /3600 because my former used approach with TIME_TO_SEC and SEC_TO_TIME only used the TIME part of the DATETIME field and thereby produced some wrong numbers. With MySQL 5.5 i could use TO_SECONDS but unfortunately my server is still runing 5.1.