Why is "INTERVAL" not a valid syntax in IF statement - mysql

I have a IF THEN ELSE statement in one of my stored prodecures.
if (DAYOFWEEK((SELECT DATE_ADD(_todaydate, INTERVAL (_sign * offset.value) DAY)))=7)
then
SELECT DATE_ADD(daytemp, INTERVAL (_sign * offset.value + 2) DAY) into `day`;
elseif (DAYOFWEEK((SELECT DATE_ADD(_todaydate, INTERVAL _sign * offset.value DAY)))=1)
then
SELECT DATE_ADD(daytemp, INTERVAL (_sign * offset.value + 1) DAY) into `day`;
else
Select (SELECT DATE_ADD(_todaydate, INTERVAL _sign * offset.value DAY)) into `day`;
END if;
where _todaydate is today's date,_sign is either +1 or -1 depending on if you want to find days in future or past and the offset is a number int showing how many days from _todaydate.
The query is meant to return me working day either in past or future depending upon the _sign * offset.value, but the problem is that sql returns me an error saying that "unexpected INTERVAL (interval) in the if statement"
I am not sure why I am getting this problem because the query it self looks fine to me. Can some one please see what I am missing...
Additional Information:
I have this IF statement in a "Select From" statement, Could that be the case of that this error is happening?

Shouldn't your IF statement be like below
if (SELECT DAYOFWEEK(DATE_ADD(_todaydate, INTERVAL (_sign * offset.value) DAY))=7)
Moreover, I see that you are repeating the same condition multiple times, eventually executing the same query multiple times. Instead have this query executed before and store the value to a local variable and use that local variable in your conditional statement rather; which would be much efficient.

Wouldn't the code be much easier to debug and understand, if we avoided repeating long expressions, wrote something like this (to accomplish what it looks like we are trying to achieve):
DECLARE _dow INT;
DECLARE _inc INT;
SELECT DAYOFWEEK(DATE_ADD(_todaydate, INTERVAL (_sign * offset.value) DAY)) INTO _dow ;
CASE _dow
WHEN 7 THEN SET _inc = 2;
WHEN 1 THEN SET _inc = 1;
ELSE SET _inc = 0;
END CASE;
SELECT DATE_ADD(daytemp, INTERVAL (_sign * offset.value + _inc) DAY) INTO `day`;
To answer the question you asked, "why is INTERVAL not a valid syntax in IF statement"...
I'm not aware of any restriction in MySQL stored programs that prohibits the use of the keyword INTERVAL within an IF statement. Obviously, the INTERVAL keyword isn't part of the IF construct; the keyword is only valid in the context of some expressions.
FOLLOWUP
I notice that the rewrite above is not equivalent to the original. In the original, under the ELSE condition, it's DATE_ADD(_todaydate,. That differs from the other two conditions, where it's DATE_ADD(daytemp
A slight re-write to accommodate that difference, adding another variable _bdt, allows us to still just use two SELECT statements...
DECLARE _bdt DATE;
DECLARE _dow INT;
DECLARE _inc INT;
SELECT DAYOFWEEK(DATE_ADD(_todaydate, INTERVAL (_sign * offset.value) DAY)) INTO _dow ;
CASE _dow
WHEN 7 THEN
SET _inc = 2;
SET _bdt = daytemp;
WHEN 1 THEN
SET _inc = 1;
SET _bdt = daytemp;
ELSE
SET _inc = 0;
SET _bdt = _todaydate;
END CASE;
SELECT DATE_ADD(_bdt, INTERVAL (_sign * offset.value + _inc) DAY) INTO `day`;

Related

MySQL Move old data between tables

I have a problem. I am trying to move data that's older than 2 months to a different table, so I created this routine code:
BEGIN
START TRANSACTION;
DECLARE _limit_date DATETIME;
SET _limit_date := DATE_SUB(NOW(), INTERVAL 2 MONTH);
INSERT
INTO UserDataHist
SELECT *
FROM UserData
WHERE DateTimeLocal < _limit_date;
DELETE
FROM UserData
WHERE DateTimeLocal < _limit_date;
COMMIT;
END
But that gives me an error somewhere in the following code:
DECLARE _limit_date DATETIME;
SET _limit_date := DATE_SUB(NOW(), INTERVAL 2 MONTH);
Now I can't find a lot about routine code examples like mine, so I have no idea what is wrong!
Can someone tell me why I my code gives me an error?

Create a loop based on date Mysql

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;

SQL: DATE_ADD(date,INTERVAL expr type) skip weekends

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.

How can I store a MySQL interval type?

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 //

Whats wrong with this short procedure (MySQL)

Im trying to make a procedure, which will be checking if user is already logged (he got a session, and im checking if his last action was over 15 minutes ago). My procedure looks like this:
CREATE PROCEDURE `isLogged`(in p_sessid VARCHAR(32), out res INT(1))
BEGIN
DECLARE v_customer_id INT(9);
DECLARE v_date DATE;
SELECT customer_id INTO v_customer_id FROM Sessions WHERE sessid=p_sessid;
SELECT expiry_date INTO v_date FROM Sessions WHERE sessid=p_sessid;
SET res=3;
IF v_customer_id > 0 THEN
IF UNIX_TIMESTAMP(NOW()) > UNIX_TIMESTAMP(v_date) THEN
DELETE FROM Sessions WHERE sessid=p_sessid;
SET res=1;
ELSE
UPDATE Sessions SET expiry_date=DATE_ADD(NOW(), INTERVAL 15 MINUTE) WHERE customer_id=v_customer_id;
SET res=0;
END IF;
END IF;
END
Can anyone tell, why it always return 1, what means that user is not logged anymore? I were checking manually expression UNIX_TIMESTAMP(NOW()) > UNIX_TIMESTAMP(v_date), and it gives me 0 in response, so? Whats going on?
Thanks in advance,
Marcin
The first IF statement should read like this:
UNIX_TIMESTAMP(NOW()) > UNIX_TIMESTAMP(DATE_ADD(v_date, INTERVAL 15 MINUTE))
or else NOW will always be greater than the last login date.
You may rewrite/optimize your procedure to function. For example -
CREATE FUNCTION `isLogged`(IN p_sessid VARCHAR(32))
RETURNS INT
BEGIN
DELETE FROM Sessions WHERE sessid = p_sessid AND v_date <= NOW() - INTERVAL 15 MINUTE;
IF ROW_COUNT() > 0 THEN -- check how many rows were deleted
RETURN 1;
ELSE
UPDATE Sessions SET expiry_date = NOW() + INTERVAL 15 MINUTE WHERE customer_id = v_customer_id;
IF ROW_COUNT() > 0 THEN -- check how many rows were updated
RETURN 0;
END IF;
END IF;
RETURN 3;
END
Also, you can try to debug your code to understand the error.
Omg, that was very stupid.
There was a type mismatch. v_date type was a DATE, and this is just a day! Like 2011-12-14.
Solution:
change DATE -> DATETIME.
And now everything works good.
Anyway, thank you for answers.