Get Date with Day of Week, Week Number, Month and Year - mysql

There some kind to get the Date if you have only the Day of Week, Week number, Month and Year with MySQL?
Example:
I Want to know which day is with this parameters:
Year : 2014
Month : Setember (09)
Week number of Year : 37 OR Week number in Setember : 3
Day of Week: Thursday
The Answer is '2014-09-18'

Using Barmars suggestion you can build a calendar of the year on the fly and check it against your constraints about that:
SET #year := 2014; -- set the year of the constraints
SET #week := 37; -- the week
SET #day_of_week := 5; -- the day of the week (numerical)
-- build the first of the wanted year as supposed by Barmar
SET #first_of_year = STR_TO_DATE(CONCAT(#year, '-01-01'), '%Y-%m-%d');
SELECT
#first_of_year + INTERVAL t.n DAY the_date
FROM (
SELECT
a.N + b.N * 10 + c.N * 100 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) c
ORDER BY n
) t
WHERE
t.n < TIMESTAMPDIFF(DAY, #first_of_year, #first_of_year + INTERVAL 1 YEAR)
AND
WEEK(#first_of_year + INTERVAL t.n DAY) = #week
AND
DAYOFWEEK(#first_of_year + INTERVAL t.n DAY) = #day_of_week
;
Demo
Note
The UNION generates the numbers from 0 to 399, so we can generate a calendar of the year. Now we can apply your other constraints like week in year and day of week.

I asked the same question in portuguese Stake Overflow, and they found a simple solution.
Using str_to_date, year, week number and day of week.
%Y Year, numeric, four digits
%U Week (00..53), where Sunday is the first day of the week
%W Weekday name (Sunday..Saturday)
SELECT str_to_date('201437 Thursday', '%Y%U %W');
Result:
2014-09-18 00:00:00
Portuguese Stack Overflow Answer Link : https://pt.stackoverflow.com/questions/33046/obter-data-com-dia-da-semana-n%C3%BAmero-da-semana-m%C3%AAs-e-ano/33063#33063
Thanks to everyone who helped me

Related

How to supplement the result set with dates for Saturday and Sunday when they are not in the table [duplicate]

I have a table with 2 columns, date and score. It has at most 30 entries, for each of the last 30 days one.
date score
-----------------
1.8.2010 19
2.8.2010 21
4.8.2010 14
7.8.2010 10
10.8.2010 14
My problem is that some dates are missing - I want to see:
date score
-----------------
1.8.2010 19
2.8.2010 21
3.8.2010 0
4.8.2010 14
5.8.2010 0
6.8.2010 0
7.8.2010 10
...
What I need from the single query is to get: 19,21,9,14,0,0,10,0,0,14... That means that the missing dates are filled with 0.
I know how to get all the values and in server side language iterating through dates and missing the blanks. But is this possible to do in mysql, so that I sort the result by date and get the missing pieces.
EDIT: In this table there is another column named UserID, so I have 30.000 users and some of them have the score in this table. I delete the dates every day if date < 30 days ago because I need last 30 days score for each user. The reason is I am making a graph of the user activity over the last 30 days and to plot a chart I need the 30 values separated by comma. So I can say in query get me the USERID=10203 activity and the query would get me the 30 scores, one for each of the last 30 days. I hope I am more clear now.
MySQL doesn't have recursive functionality, so you're left with using the NUMBERS table trick -
Create a table that only holds incrementing numbers - easy to do using an auto_increment:
DROP TABLE IF EXISTS `example`.`numbers`;
CREATE TABLE `example`.`numbers` (
`id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Populate the table using:
INSERT INTO `example`.`numbers`
( `id` )
VALUES
( NULL )
...for as many values as you need.
Use DATE_ADD to construct a list of dates, increasing the days based on the NUMBERS.id value. Replace "2010-06-06" and "2010-06-14" with your respective start and end dates (but use the same format, YYYY-MM-DD) -
SELECT `x`.*
FROM (SELECT DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY)
FROM `numbers` `n`
WHERE DATE_ADD('2010-06-06', INTERVAL `n`.`id` -1 DAY) <= '2010-06-14' ) x
LEFT JOIN onto your table of data based on the time portion:
SELECT `x`.`ts` AS `timestamp`,
COALESCE(`y`.`score`, 0) AS `cnt`
FROM (SELECT DATE_FORMAT(DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY), '%m/%d/%Y') AS `ts`
FROM `numbers` `n`
WHERE DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY) <= '2010-06-14') x
LEFT JOIN TABLE `y` ON STR_TO_DATE(`y`.`date`, '%d.%m.%Y') = `x`.`ts`
If you want to maintain the date format, use the DATE_FORMAT function:
DATE_FORMAT(`x`.`ts`, '%d.%m.%Y') AS `timestamp`
I'm not a fan of the other answers, requiring tables to be created and such. This query does it efficiently without helper tables.
SELECT
IF(score IS NULL, 0, score) AS score,
b.Days AS date
FROM
(SELECT a.Days
FROM (
SELECT curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY AS Days
FROM (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS a
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS b
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS c
) a
WHERE a.Days >= curdate() - INTERVAL 30 DAY) b
LEFT JOIN your_table
ON date = b.Days
ORDER BY b.Days;
So lets dissect this.
SELECT
IF(score IS NULL, 0, score) AS score,
b.Days AS date
The if will detect days that had no score and set them to 0. b.Days is the configured amount of days you chose to get from the current date, up to 1000.
(SELECT a.Days
FROM (
SELECT curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY AS Days
FROM (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS a
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS b
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS c
) a
WHERE a.Days >= curdate() - INTERVAL 30 DAY) b
This subquery is something I saw on stackoverflow. It efficiently generates a list of the past 1000 days from the current date. The interval (currently 30) in the WHERE clause at the end determines which days are returned; the maximum is 1000. This query could be easily modified to return 100s of years worth of dates, but 1000 should be good for most things.
LEFT JOIN your_table
ON date = b.Days
ORDER BY b.Days;
This is the part that brings your table that contains the score into it. You compare to the selected date range from the date generator query to be able to fill in 0s where needed (the score will be set to NULL initially, because it is a LEFT JOIN; this is fixed in the select statement). I also order it by the dates, just because. This is preference, you could also order by score.
Before the ORDER BY you could easily join with your table about user info you mentioned with your edit, to add that last requirement.
I hope this version of the query helps someone. Thanks for reading.
Time went by since this question was asked. MySQL 8.0 was released in 2018 and added support for recursive common table expressions, which provide an elegant, state-of-the-art solution to this question.
The following query can be used to generate a list of dates, say for the first 15 days of August 2010:
with recursive all_dates(dt) as (
-- anchor
select '2010-08-01' dt
union all
-- recursion with stop condition
select dt + interval 1 day from all_dates where dt < '2010-08-15'
)
select * from all_dates order by dt
You can then left join this resultset with your table to generate the expected output:
with recursive all_dates(dt) as (
select '2010-08-01' dt
union all
select dt + interval 1 day from all_dates where dt < '2010-08-15'
)
select d.dt date, coalesce(t.score, 0) score
from all_dates d
left join mytable t on t.date = d.dt
order by d.dt
Demo on DB Fiddle:
date | score
:--------- | ----:
2010-08-01 | 19
2010-08-02 | 21
2010-08-03 | 0
2010-08-04 | 14
2010-08-05 | 0
2010-08-06 | 0
2010-08-07 | 10
2010-08-08 | 0
2010-08-09 | 0
2010-08-10 | 14
2010-08-11 | 0
2010-08-12 | 0
2010-08-13 | 0
2010-08-14 | 0
2010-08-15 | 0
Note that it is very easy to adapt the recursive CTE for other intervals or periods. As an example, say we want a row every 15 minutes from 4 AM to 8 AM on August 1st, 2010 ; we can do :
with recursive all_dates(dt) as (
select '2010-08-01 04:00:00' dt
union all
select dt + interval 15 minute from all_dates where dt < '2010-08-01 08:00:00'
)
...
You can accomplish this by using a Calendar Table. That's a table which you create once and fill with a date range (e.g. one dataset for each day 2000-2050; that depends on your data). Then you can make an outer join of your table against the calendar table. If a date is missing in your table, you return 0 for the score.
Michael Conard answer is great but I needed intervals of 15 minutes where the time must always start at the top of every 15th minute:
SELECT a.Days
FROM (
SELECT FROM_UNIXTIME( FLOOR( UNIX_TIMESTAMP() / (15 * 60) ) * (15 * 60)) - INTERVAL 15 * (a.a + (10 * b.a) + (100 * c.a)) MINUTE AS Days
FROM (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS a
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS b
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS c
) a
WHERE a.Days >= curdate() - INTERVAL 30 DAY
This will set the current time to the previous round 15th minute:
FROM_UNIXTIME( FLOOR( UNIX_TIMESTAMP() / (15 * 60) ) * (15 * 60))
And this will remove time with a 15 minute step:
- INTERVAL 15 * (a.a + (10 * b.a) + (100 * c.a)) MINUTE
If there's a simpler way to do it, please let me know.
you can user direct from start date up to today with insertion
with recursive all_dates(dt) as (
-- anchor
select '2021-01-01' dt
union all
-- recursion with stop condition
INSERT IGNORE INTO mytable (date,score) VALUES (dt + interval 1 day ,0 ) where dt + interval 1 day <= curdate()
)
select * from all_dates

MYSQL calculate current year 1st month, 2nd month etc from old dates

I am looking for a query in MYSQL that would allow me to obtain the equivalent date for each month of the current year from old dates so for example The date: '2005-01-31' I would like to see the following populated into 12 separate month fields:
Jan - '2021-01-31',
Feb - '2021-02-28',
Mar - '2021-03-31'
I have attempted the following query however this only populates the same month of the old date but does however show the current equivalent day and year:
Select DATE_FORMAT(DATE_ADD('2005-01-31', INTERVAL (YEAR(CURRENT_DATE()) - YEAR('2005-01-31') ) YEAR), '%Y-%m-%d') `date`;
'2021-01-31'
An example for a few months would be much appreciated and I should be able to adapt for the rest of the calendar year myself.
WITH RECURSIVE
cte AS ( SELECT 0 n
UNION ALL
SELECT n + 1 FROM cte WHERE n < 11 )
SELECT DATE_FORMAT(#date, '2021-%m-%d') + INTERVAL n MONTH `date`
FROM cte
fiddle
does not work for me on MySQL 5.x
SELECT DATE_FORMAT(#date, '2021-%m-%d') + INTERVAL n MONTH `date`
FROM (SELECT 0 n 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 UNION SELECT 10 UNION SELECT 11 ) cte
fiddle

How to query months and number of days in a month of current year in SQL?

I need a table of 2 columns and 12 rows as a result of MySQL/SQL query:
First column is 12 months of the current year.
Second column is the number of days in each month.
Table should look similar to:
months days
January 31
February 28
... ...
December 31
I tried:
DELIMITER $$
DROP PROCEDURE IF EXISTS my_loop$$
CREATE PROCEDURE my_loop()
BEGIN
DECLARE x INT;
SET x = 1;
WHILE x <= 12 DO
SELECT monthname(SUBDATE("2019-12-01", INTERVAL x month)) as months, day(LAST_DAY(SUBDATE("2019-12-01", INTERVAL x month))) as days;
SET x = x + 1;
END WHILE;
END$$
DELIMITER ;
call my_loop();
But it prints out only the first iteration:
months days
January 31
How can I solve this with or without an iteration?
You can do this with a query:
select monthname(makedate(year(curdate()), 1) + interval (x.mon - 1) month) as month_name,
day(last_day(makedate(year(curdate()), 1) + interval (x.mon - 1) month)) as days_in_month
from (select 1 as mon union all select 2 union all select 3 union all select 4 union all
select 5 as mon union all select 6 union all select 7 union all select 8 union all
select 9 as mon union all select 10 union all select 11 union all select 12
) x
order by x.mon;
Here is a db<>fiddle.

Convert Month into weeks in MySQL

I am looking to breakdown a Month into weeks(starting Monday) in MySQL.
For example,
Sept 3 - 9th
Sept 10 - 16th
How to achieve this using MySql?
Thanks in advance.
This query will produce the results you want. It generates a table of possible week numbers in the month (0 to 4) using a UNION, and then it adds those week numbers to the computation of the first Monday of the month (stored in the variable #firstday which is JOINed to the table of week numbers).
SELECT #firstday + INTERVAL w WEEK AS start, #firstday + INTERVAL w WEEK + INTERVAL 6 DAY AS end
FROM (SELECT 0 AS w UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) weeks
JOIN (SELECT #firstday := FROM_DAYS(TO_DAYS(CURDATE())-DAY(CURDATE())+1) +
(7 - WEEKDAY(FROM_DAYS(TO_DAYS(CURDATE())-DAY(CURDATE())+1))) % 7) f
HAVING end <= LAST_DAY(#firstday)
Output:
start end
2018-09-03 2018-09-09
2018-09-10 2018-09-16
2018-09-17 2018-09-23
2018-09-24 2018-09-30
To run the query for any given month, replace CURDATE() in the computation of #firstday (4 places) with a date in the month you are interested in e.g.
SELECT #firstday + INTERVAL w WEEK AS start, #firstday + INTERVAL w WEEK + INTERVAL 6 DAY AS end
FROM (SELECT 0 AS w UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) weeks
JOIN (SELECT #firstday := FROM_DAYS(TO_DAYS('2018-01-20')-DAY('2018-01-20')+1) +
(7 - WEEKDAY(FROM_DAYS(TO_DAYS('2018-01-20')-DAY('2018-01-20')+1))) % 7) f
HAVING end <= LAST_DAY(#firstday)
Output:
start end
2018-01-01 2018-01-07
2018-01-08 2018-01-14
2018-01-15 2018-01-21
2018-01-22 2018-01-28
If you have the flexibility of setting variables, you can clean up the query like so:
SET #day = '2018-01-20';
SET #firstday := FROM_DAYS(TO_DAYS(#day)-DAY(#day)+1) + (7 - WEEKDAY(FROM_DAYS(TO_DAYS(#day)-DAY(#day)+1))) % 7;
SELECT #firstday + INTERVAL w WEEK AS start, #firstday + INTERVAL w WEEK + INTERVAL 6 DAY AS end
FROM (SELECT 0 AS w UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) weeks
HAVING end <= LAST_DAY(#firstday)
You can use function week(date) - https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_week

How to get the all mondays in a month?

I want to get all the Mondays in the month of MAY 2015
(using mysql query)
OUTPUT:
MON
04
11
18
25
select row+1 as Mon from
( SELECT #row := #row + 1 as row FROM
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6) t1,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6) t2,
(SELECT #row:=-1) t3 limit 31 ) b where
DATE_ADD('2015-05-01', INTERVAL ROW DAY) between '2015-05-01' and '2015-05-31' and DAYOFWEEK(DATE_ADD('2015-05-01', INTERVAL ROW DAY))=2;
Output
+------------+
| Mon |
+------------+
| 4 |
| 11 |
| 18 |
| 25 |
+------------+
Tweaking a bit this query
For reference, here's another solution - note that the last entry may be null, can be changed to another value if necessary, or wrap in a sub-select and filter on not null.
SET #date='2015-05-01';
SET #offset=7 - WeekDay(#date);
SELECT DAY(DATE_ADD(#date,INTERVAL #offset DAY)) AS 'MON'
UNION SELECT DAY(DATE_ADD(#date,INTERVAL #offset+7 DAY))
UNION SELECT DAY(DATE_ADD(#date,INTERVAL #offset+14 DAY))
UNION SELECT DAY(DATE_ADD(#date,INTERVAL #offset+21 DAY))
UNION DISTINCT SELECT IF(DAY(DATE_ADD(#date,INTERVAL #offset+28 DAY))>21,
DAY(DATE_ADD(#date,INTERVAL #offset+28 DAY)),
DAY(DATE_ADD(#date,INTERVAL #offset+21 DAY)))
;
SQL Fiddle: http://sqlfiddle.com/#!9/fa4ce/4
This query returns the two digit day value of the Mondays in a month.
This requires the "month" as a date of the first day of the month, as a value in the SELECT list of the first inline view (d0). (This inline view query could be tweaked to handle any date value within a month as the specification for a month.)
SELECT DATE_FORMAT(d0.dt + INTERVAL d1.i*6+d2.i DAY,'%d') AS dd
-- , d0.dt + INTERVAL d1.i*6+d2.i DAY AS dt
FROM ( SELECT '2015-05-01' + INTERVAL 0 DAY AS dt
) d0
CROSS
JOIN ( SELECT 0 AS i UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
) d1
CROSS
JOIN ( SELECT 0 AS i UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
) d2
WHERE d0.dt + INTERVAL d1.i*6+d2.i DAY < d0.dt + INTERVAL 1 MONTH
AND NOT WEEKDAY(d0.dt + INTERVAL d1.i*6+d2.i DAY)
ORDER BY 1
NOTE: This requires the month to be specified only once, in the first inline view (d0). Everything else is handled in expression that reference this one value.
The WEEKDAY function returns 0 for a date value that is a Monday, so a NOT on the return from the WEEKDAY function will return TRUE for a Monday.
For a supplied date value of '2015-05-01', this returns:
dd
--
04
11
18
25