I am developing a Java application using MySQL. I need to know which is the week of each month, of the stored dates. Is there any MySQL function for that ? Basically , if i was to use this for the current date (13.09) it would show me its in week number 2 and tomorrow it will be week number 3.
You can play with the WEEK() function, and see if it suits your needs. Here I'm using WEEK(date, 3) that will return the week of the year from 1 to 53, starting from Mondays:
set #my_date = '2015-09-13';
SELECT
WEEK(#my_date, 3) -
WEEK(#my_date - INTERVAL DAY(#my_date)-1 DAY, 3) + 1
AS week_number;
WEEK(date, 3) will return the week of the year of the selected date
WEEK(date - INTERVAL DAY(#my_date)-1 DAY, 3) will return the week of the year of the first day of the month of the selected date
It will return 1 for 01-March-2015 (because it's the first day of the month so it's week 1) and 2 for 02-March-2015 (because weeks starts from Mondays, so it's a new week). If this is not the desidered behaviour you should specify your requirements more precisely.
Please see a fiddle here.
Unfortunately, there isn't a "weekofmonth" function, but you could use dayofmonth, and manipulate the result a bit:
SELECT CURRENT_DATE(),
FLOOR((DAYOFMONTH(CURRENT_DATE()) - 1) / 7) + 1 AS week_of_month
Create a mysql function.
CREATE FUNCTION `WEEK_OF_MONTH`(
datee DATE
) RETURNS INT(11)
BEGIN
DECLARE DayNamee VARCHAR(20);
DECLARE StartDatee DATE;
DECLARE DayNumber INT DEFAULT 0;
SET DayNamee = (SELECT DAYNAME(datee));
SET StartDatee = (SELECT FIRST_DAY(datee));
WHILE StartDatee <= datee DO
IF DayNamee = DAYNAME(StartDatee) THEN
SET DayNumber = DayNumber + 1;
END IF;
SET StartDatee = DATE_ADD( StartDatee, INTERVAL 1 DAY);
END WHILE;
RETURN DayNumber;
END$$
DELIMITER ;
Call as --
SELECT `WEEK_OF_MONTH`('2018-12-31');
Result : 5
Related
I have a query :
insert into fookoo_business
select stat_date, sum(spend), sum(revenue)
from hooloo_business;
that i want to run for each date from '2017-01-20' until yesterday (it means the query will run 434 times if we're at 01/04/2018), for each date separately
(in a loop).
how can i create a loop in Mysql to do it for me?
I have tried:
creating procedure for the query select #stat_date, sum(spend), sum(revenue)
I called 'query'
then :
CREATE PROCEDURE loop_procedure()
BEGIN
SET #stat_date='2018-03-20';
CALL 'query';
REPEAT
SET #stat_date = #stat_date + INTERVAL 1 DAY;
UNTIL #stat_date = CURDATE() END REPEAT;
END
eventually i've used the following logic within a stored procedure to fetch the data:
PROCEDURE `x_monitoring_loop`()
BEGIN
DECLARE i INT;
DECLARE len INT;
SET len = 434;
SET i = 0;
WHILE (i < len) DO
SET #stat_date= CURDATE()-INTERVAL 1 DAY;
SET #stat_date= #stat_date- INTERVAL i DAY;
Insert query;
SET i = i +1;
END WHILE;
This way the query ran 434 times for each day, beginning at current date - 1 day.
I do not know why you want to use a procedure,I think we can just use a query sql to do it:
INSERT INTO fookoo_business
SELECT stat_date, SUM(spend), SUM(revenue)
FROM hooloo_business
WHERE stat_date BETWEEN STR_TO_DATE('2017-01-02', '%Y-%m-%d') -- start date
AND DATE_SUB(NOW(), INTERVAL 1 DAY) -- end date
GROUP BY stat_date;
I'm currently using DATE_ADD(date,INTERVAL expr type) to set a due date as a trigger in a mySQL Database.
What I'm wanting to know is if it is possible to skip weekends (Saturday, Sunday) as part of the trigger.
You'd have to create an own function for doing that. You can look how to do that in this answer, for example (just use function instead of procedure). As for how to write such a function, here's a working algorithm. The code is quite straightforward: it loops through days and skips weekends.
CREATE FUNCTION `DAYSADDNOWK`(addDate DATE, numDays INT) RETURNS date
BEGIN
IF (WEEKDAY(addDate)=5) THEN
SET addDate=DATE_ADD(addDate, INTERVAL 1 DAY);
END IF;
IF (WEEKDAY(addDate)=6) THEN
SET addDate=DATE_ADD(addDate, INTERVAL 1 DAY);
END IF;
WHILE numDays>0 DO
SET addDate=DATE_ADD(addDate, INTERVAL 1 DAY);
IF (WEEKDAY(addDate)=5) THEN
SET addDate=DATE_ADD(addDate, INTERVAL 1 DAY);
END IF;
IF (WEEKDAY(addDate)=6) THEN
SET addDate=DATE_ADD(addDate, INTERVAL 1 DAY);
END IF;
SET numDays=numDays-1;
END WHILE;
RETURN addDate;
END
Currently SELECT DAYSADDNOWK(CURDATE(), 5) yields 2016-03-07, which is correct.
Of course you only can use it with days, so no arbitrary interval, but your question mentioned date datatype, and I don't quite see how one could add a month not counting working days.
This function simply creates a list of dates starting at the date given in the arguments, and then figures out which date is x number of days (the interval) out while disregarding days 1 and 7 (which are Sunday and Saturday respectively on SQL Server).
CREATE FUNCTION [dbo].[udf_days_add_no_wknd]
(
#start_date date
, #interval int
)
RETURNS date
AS
BEGIN
declare #answer date
; with dates as
(
select #start_date as date_val
union all
select dateadd(d, 1, date_val) as date_val
from dates
where date_val < dateadd(d, #interval * 10, #start_date)
)
, final as
(
select top 1 lead(ld.date_val, #interval, NULL) over (order by ld.date_val asc) as new_date_val
from dates as ld
where 1=1
and datepart(dw, ld.date_val) not in (1,7) --eliminating weekends
)
select #answer = (select new_date_val from final)
return #answer
END
It is worth nothing that this solution is dependent on having SQL Server 2012 or later, considering the use of the lead() function.
What I am trying to do, is to set beginning of time interval, if that is not correctly set into stored procedure. However, it somehow does not work very well..
This is my code:
CREATE PROCEDURE intervals_generator (IN start DATETIME, IN ending DATETIME, IN intervalis INT)
BEGIN
-- temp values
DECLARE next_date DATETIME;
-- result temp values
DECLARE start_temp DATETIME;
DECLARE ending_temp DATETIME;
-- date formatting variables
DECLARE year CHAR(20);
DECLARE month CHAR(20);
DECLARE day CHAR(20);
DECLARE new_start CHAR(20);
-- SET starting date if is incorrect DATE_FORMAT(NOW(), '%d %m %Y')
SET year := DATE_FORMAT(start, '%Y');
SET month := DATE_FORMAT(start, '%c');
SET day := DATE_FORMAT(start, '%e');
IF intervalis = '1_day' THEN
BEGIN
SET new_start := year+' '+month+' '+day+' 00:00:00';
END;
ELSEIF intervalis = '1_month' THEN
BEGIN
SET new_start := year+' '+month+' 1 00:00:00';
END;
ELSEIF intervalis = '1_quarter' THEN
BEGIN
IF MONTH(start) IN (2, 3) THEN
SET month := 1;
ELSEIF MONTH(start) IN (5, 6) THEN
SET month := 4;
ELSEIF MONTH(start) IN (8, 9) THEN
SET month := 7;
ELSEIF MONTH(start) IN (11, 12) THEN
SET month := 10;
END IF;
SET new_start := year+' '+month+' 1 00:00:00';
END;
ELSEIF intervalis = '1_year' THEN
BEGIN
SET new_start := year+' 1 1 00:00:00';
END;
END IF;
SET start := STR_TO_DATE(new_start, '%Y %c %e %h:%i:%s');
SELECT year, month, day, start;
DROP TEMPORARY TABLE IF EXISTS intervals_result;
END//
DELIMITER ;
I have tried many different formattings settings and functions, but the output is still wrong, like this:
mysql> CALL intervals_generator('2013-02-01 00:00:00', '2015-12-31 00:00:00', '1_year');
+------+-------+------+---------------------+
| year | month | day | start |
+------+-------+------+---------------------+
| 2013 | 2 | 1 | 2016-00-00 00:00:00 |
+------+-------+------+---------------------+
1 row in set (0.02 sec)
Query OK, 0 rows affected, 1 warning (0.02 sec)
I really dont understand why output is "2016-00-00" instead of "2013-01-01". year, month and day variables are defined as CHAR and also function that extracts them from datetime should be returning CHAR. And function STR_TO_DATE should also been taking CHAR format, so it is a mystery for me.
If anyone has some idea, please give me hint.
If you work in DATEs instead of strings, you can make use of MySQL's date functions and operators and make everything a whole lot simpler... but not too simple because this is MySQL.
The problem with MySQL and dates is its date functionality is a real mish-mash that sometimes works with DATEs, sometimes with strings, sometimes with integers, and is missing basic functionality. It lacks a simple function to set a piece of a date; there's no function to change the MONTH part of a DATE to February. There's not even a good way to make a date from the year, month and day, closest thing you get is MAKEDATE() which takes a year and the day of the year (?!). Fortunately, DATEs in MySQL respond to math operations and it's better than messing with strings.
If you have, for example, 2013-02-12 and want 2013-02-01 you have to first make a new date with just the year using MAKEDATE, then add the month part.
-- 2013-01-01
SET new_date := MAKEDATE(YEAR(old_date), 1);
-- 2013-02-01
-- Since MONTH returns from 1 to 12, you need to take away one.
SET new_date := new_date + (INTERVAL MONTH(old_date) - 1) MONTH;
After chopping out all the unused variables, changing to date math, and using the CASE statement instead of a big IF/ELSE chain, we get this:
CREATE PROCEDURE intervals_generator (IN start_date DATE, IN intervals TEXT)
BEGIN
DECLARE new_start DATE;
CASE intervals
WHEN '1_day' THEN
-- Nothing to do, DATE has already truncated the time portion.
SET new_start := start_date;
WHEN '1_month' THEN
-- Set to the year and month of the start date
SET new_start := MAKEDATE(YEAR(start_date), 1) + INTERVAL (MONTH(start_date) - 1) MONTH;
WHEN '1_quarter' THEN
BEGIN
-- Set to the year and month of the start date
SET new_start := MAKEDATE(YEAR(start_date), 1) + INTERVAL (MONTH(start_date) - 1) MONTH;
-- Subtract the necessary months for the beginning of the quarter
SET new_start := new_start - INTERVAL (MONTH(new_start) - 1) % 3 MONTH;
END;
WHEN '1_year' THEN
-- Set the date to the first day of the year
SET new_start := MAKEDATE(YEAR(start_date), 1);
END CASE;
SELECT new_start;
END//
Try it out.
This statement is not doing what you expect:
SET new_start := year+' '+month+' '+day+' 00:00:00';
In MySQL, the + operator does addition. That's it, not concatenation.
I think you intend:
SET new_start := concat(year, ' ', month, ' ', day, ' 00:00:00');
I haven't looked at the rest of the logic to see if it makes sense, but this is one glaring problem.
Instead of slicing and building new date from parts(concatenating) which is error-prone you can use built-in DATE_FORMAT:
SqlFiddleDemo
SET #date = '2013-05-03 10:05:00';
SELECT CAST(#date AS DATETIME) AS Date,
DATE_FORMAT(#date ,'%Y-01-01 00:00:00') AS Year_Allign,
CASE EXTRACT(QUARTER FROM #date)
WHEN 1 THEN DATE_FORMAT(#date ,'%Y-01-01 00:00:00')
WHEN 2 THEN DATE_FORMAT(#date ,'%Y-04-01 00:00:00')
WHEN 3 THEN DATE_FORMAT(#date ,'%Y-07-01 00:00:00')
WHEN 4 THEN DATE_FORMAT(#date ,'%Y-10-01 00:00:00')
ELSE NULL END AS Quarter_Allign,
DATE_FORMAT(#date ,'%Y-%m-01 00:00:00') AS Month_Allign,
DATE_FORMAT(#date ,'%Y-%m-%d 00:00:00') AS Day_Allign;
SqlFiddleDemo2
SET #date = '2013-05-03 10:05:00';
SET #allign = '1_QUARTER';
SELECT
CAST(#date AS DATETIME) AS Date,
CASE #allign
WHEN '1_YEAR' THEN DATE_FORMAT(#date ,'%Y-01-01 00:00:00')
WHEN '1_QUARTER' THEN (CASE EXTRACT(QUARTER FROM #date)
WHEN 1 THEN DATE_FORMAT(#date ,'%Y-01-01 00:00:00')
WHEN 2 THEN DATE_FORMAT(#date ,'%Y-04-01 00:00:00')
WHEN 3 THEN DATE_FORMAT(#date ,'%Y-07-01 00:00:00')
WHEN 4 THEN DATE_FORMAT(#date ,'%Y-10-01 00:00:00')
ELSE NULL END)
WHEN '1_MONTH' THEN DATE_FORMAT(#date ,'%Y-%m-01 00:00:00')
WHEN '1_DAY' THEN DATE_FORMAT(#date ,'%Y-%m-%d 00:00:00')
ELSE NULL
END AS Alligned;
i have date field and i query for selecting Birthday before 7 days in mysql.
for example
if
birth = 1986-08-05
if now is 2012-07-30 this query alarm me.
or
birth = 1986-01-05
if now is 2012-12-30 this query alarm me.
birth is field of user_table.
select * from user_table
where date_format(date_sub(birth, interval 7 days), "%m-%d")
= date_format(now(), "%m-%d")
or date_format(date_sub(birth, interval 7 days), "%m-%d") = '02-29'
and month(now()) = 2 and month(date_add(now(), interval 1 day)) = 3
i find my select for this goal.
select *,birthdate,
concat(if(date_format(birthdate, '%m') = '12',date_format(curdate(), "%Y")
,date_format(now(), "%Y")),
date_format(date_add(curdate(), interval 7 day), '%Y')) as birthday
from users
HAVING birthday BETWEEN curdate() and date_add(curdate(), interval 7 day)
thanks from your help.
Use SQL functions if you want reusable code and want to keep your SQL easy to read and maintain. A birthday is an anniversary, so...
DROP FUNCTION IF EXISTS anniversary_after;
DELIMITER $$
CREATE FUNCTION anniversary_after(anydate DATE, after DATE)
RETURNS DATE DETERMINISTIC
BEGIN
DECLARE anniversary DATE;
DECLARE years INTEGER;
SET years = YEAR(after) - YEAR(anydate);
SET anniversary = DATE_ADD(anydate, INTERVAL years YEAR);
IF anniversary < after THEN
SET anniversary = DATE_ADD(anniversary, INTERVAL 1 YEAR);
END IF;
RETURN anniversary;
END
$$
DELIMITER ;
DROP FUNCTION IF EXISTS anniversary;
CREATE FUNCTION anniversary(anydate DATE)
RETURNS DATE DETERMINISTIC
RETURN anniversary_after(anydate, CURRENT_DATE());
Show date of next anniversary:
SELECT anniversary('1994-04-05');
Show days until next anniversary:
SELECT DATEDIFF(anniversary('1994-04-05'), CURRENT_DATE());
something like this?
SELECT user_name, DATE_SUB(birth, INTERVAL 7 DAYS) as SevenDaysBefore from user_table
This is what I would like to be able to do:
SET #interval_type := MONTH;
SELECT '2012-01-01' + INTERVAL 6 #interval_type;
+------------+
|'2012-06-01'|
+------------+
And of course that doesn't work and there is no "interval" data type in MySQL.
I want to be able to store an interval value and an interval type in a table so that i can have the database quickly do the math naturally without having to write a big switch statement, ala
... ELSE IF (type = 'MONTH') { SELECT #date + INTERVAL #value MONTH; } ...
Is this supported in any way in MySQL or do you have a clever hack for this?
Thanks; you rock.
This solution may come handy to somebody implementing the job queue for cron or something similar.
Let us suppose we have a reference date (DATETIME) and interval of repetition. We would like to store both values in database and get the quick comparison whether it's already time to execute and include job into execution queue or not.
The interval could be non trivial e.g. (1 YEAR 12 DAYS 12 HOUR) and is controlled by wise user (admin) so that user is not going to use values exceeding the range of regular DATETIME data type or otherwise the conversion must be implemented first. (18 MONTH -> 1 YEAR 6 MONTH).
We can use then DATETIME data type for storing both values reference date and interval. We can define stored function using:
DELIMITER $$
CREATE DEFINER=`my_db`#`%` FUNCTION `add_interval`(`source` DATETIME, `interval` DATETIME) RETURNS datetime
BEGIN
DECLARE result DATETIME;
SET result = `source`;
SET result=DATE_ADD(result, INTERVAL EXTRACT(YEAR FROM `interval`) YEAR);
SET result=DATE_ADD(result, INTERVAL EXTRACT(MONTH FROM `interval`) MONTH);
SET result=DATE_ADD(result, INTERVAL EXTRACT(DAY FROM `interval`) DAY);
SET result=DATE_ADD(result, INTERVAL EXTRACT(HOUR FROM `interval`) HOUR);
SET result=DATE_ADD(result, INTERVAL EXTRACT(MINUTE FROM `interval`) MINUTE);
SET result=DATE_ADD(result, INTERVAL EXTRACT(SECOND FROM `interval`) SECOND);
RETURN result;
END
We can then make DATETIME arithmetic using this function e.g.
// test solution
SELECT add_interval('2014-07-24 15:58:00','0001-06-00 00:00:00');
// get job from schedule table
SELECT job FROM schedule WHERE add_interval(last_execution,repetition)<NOW();
// update date of executed job
UPDATE schedule SET last_execution=add_interval(last_execution,repetition);
You can solve this problem using prepared statements, considering there is no language construct available for use. The benefit here being you get the performance and flexibility that you want; this could easily be placed in a stored procedure or function for added value:
SET #date = '2012-01-01';
SET #value = 6;
SET #type = 'MONTH';
SET #q = 'SELECT ? + INTERVAL ? ';
SET #q = CONCAT(#s, #type);
PREPARE st FROM #q;
EXECUTE st USING #date, #value;
Alternatively, depending on your database / software architecture and the type of date/time intervals you are thinking of, you could simply this problem by using a time-scale interval:
SELECT #date + INTERVAL #value SECOND
1 second - 1
1 minute - 60
1 hour - 3600
1 day - 86400 (24 hours)
1 week - 604800 (7 days)
1 month - 2419200 (4 weeks)
Here's the simplistic approach. It works reasonably fast. You can change the order of the switch statements to optimize for speed if you feel that you will be hitting some more often then others. I have not benched this against Chris Hutchinson's solution. I ran into problems trying to wrap it into a nice function because of the dynamic SQL. Anyway, for posterity, this is guaranteed to work:
CREATE FUNCTION AddInterval( date DATETIME, interval_value INT, interval_type TEXT )
RETURNS DATETIME
DETERMINISTIC
BEGIN
DECLARE newdate DATETIME;
SET newdate = date;
IF interval_type = 'YEAR' THEN
SET newdate = date + INTERVAL interval_value YEAR;
ELSEIF interval_type = 'QUARTER' THEN
SET newdate = date + INTERVAL interval_value QUARTER;
ELSEIF interval_type = 'MONTH' THEN
SET newdate = date + INTERVAL interval_value MONTH;
ELSEIF interval_type = 'WEEK' THEN
SET newdate = date + INTERVAL interval_value WEEK;
ELSEIF interval_type = 'DAY' THEN
SET newdate = date + INTERVAL interval_value DAY;
ELSEIF interval_type = 'MINUTE' THEN
SET newdate = date + INTERVAL interval_value MINUTE;
ELSEIF interval_type = 'SECOND' THEN
SET newdate = date + INTERVAL interval_value SECOND;
END IF;
RETURN newdate;
END //
It comes with this equally simplistic benchmark test:
CREATE FUNCTION `TestInterval`( numloops INT )
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE date DATETIME;
DECLARE newdate DATETIME;
DECLARE i INT;
SET i = 0;
label1: LOOP
SET date = FROM_UNIXTIME(RAND() * 2147483647);
SET newdate = AddInterval(date,1,'YEAR');
SET i = i+1;
IF i < numloops THEN
ITERATE label1;
ELSE
LEAVE label1;
END IF;
END LOOP label1;
return i;
END //