I have a table with a start_date, end_date and an interval. I would like to update end_date with the value of start_date and the interval.
create table date_test (
start_date date,
end_date date,
date_interval varchar(45)
);
The values I am using for date_interval are like - INTERVAL 1 WEEK, + INTERVAL 1 MONTH.
I would like to do something like:
UPDATE date_test SET end_date = date( concat( start_date, " ", date_interval));
but I get this warning:
1292 Truncated incorrect date value: '2012-01-01 - INTERVAL 1 week'
How can I force this date to get evaluated before updating?
Jonathan Leffler said :
Nearly; there's a crucial difference between the manual page and the question, though. The manual discusses DATE_ADD(date_value, INTERVAL '1' DAY) etc, whereas the question would be having a 'string' value as the second parameter. I fear the question would need a function to convert the string into an INTERVAL type. There doesn't appear to be a 'TO_INTERVAL' function in MySQL.
Here is a function that will take the date as first parameter and the string interval as second parameter.
Simply add the following stored procedure to your database :
CREATE PROCEDURE my_date_add(d DATE, i VARCHAR(50))
BEGIN
DECLARE sign CHAR(1);
DECLARE x INT;
SET sign = SUBSTRING_INDEX(i, ' ', 1);
SET x = SUBSTRING_INDEX(SUBSTRING_INDEX(i, ' ', -2), ' ', 1);
IF sign = '-' THEN
SET x = -x;
END IF;
CASE SUBSTRING_INDEX(i, ' ', -1)
WHEN 'DAY' THEN SELECT DATE_ADD(d, INTERVAL x DAY);
WHEN 'WEEK' THEN SELECT DATE_ADD(d, INTERVAL x WEEK);
WHEN 'MONTH' THEN SELECT DATE_ADD(d, INTERVAL x MONTH);
END CASE;
END
Then you should be able to update your table like this :
UPDATE date_test SET end_date = my_date_add(start_date, date_interval);
What you want to do is :
UPDATE date_test SET end_date = DATE_ADD(start_date, date_interval);
But I'm not sure that using date_interval as the second parameter will work, tell us if it does.
You will find a lot of useful examples in the MySQL documentation, see DATE_ADD() function description.
MySQL does not support values evaluating. So, you cannot use an UPDATE statement directly.
In this case I'd suggest you these ways:
Write a SELECT...INTO OUTFILE statement that would generate a list of UPDATE statemants and output all this statements into the file, then just run this sript.
Or write a stored procedure that would open a cursor on date_test table, in a loop generate and execute UPDATE statements for each record, one by one, using prepared statements.
Ask, if you have a questions about the solutions.
Related
I am (unsuccessfully) trying to set as default value to DATETIME a date based on another column that uses CURRENT_TIMESTAMP as default, but adding some days.
On MSSQL I used to do (dateadd(day,(5),[start_date])) as a "Computed Column Specification" to set the column end_date 5 days more that start_date column.
So, when I perform an INSERT I would like that start_date were set to NOW(); and end_date were set to [NOW(); + X days]
Is this even possible on MySQL?
Thanks in advance!
If you use an older version of MySQL and cannot use expressions in the DEFAULT clause as shown in the other answer, you can do this with a trigger.
CREATE TRIGGER mytrigger BEFORE INSERT ON t1
FOR EACH ROW BEGIN
SET NEW.start_date = CURDATE();
SET NEW.end_date = CURDATE() + INTERVAL 5 DAY;
END
As of MySQL 8.0.13, you can use expressions for default values.
It would allow you to call functions in your default. However, I do not believe you have the ability to query other columns (give it a try?)
You can use something like:
CREATE TABLE t1 (
...
start_date DATE DEFAULT (CURRENT_DATE),
end_date DATE DEFAULT (CURRENT_DATE + INTERVAL 5 DAY),
...
);
Do note the enclosing parenthesis is required for indicating it is an expression and not a literal.
Reference: https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html
I want to both create a temporary table and utilize a variable. I have consulted the documentation and I believe the syntax is correct but putting them together is giving me an error.
My goal is to create a date variable that is always 1 year before now() and will populate my temporary table with one month increment rows until the present moment. Any help would be greatly appreciated.
Error:
"Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SET #start_date = DATEADD(year, -1 ,GETDATE())\n\n WHILE #start_date < GETD' at line 4"
Here is my SQL statement.
CREATE TEMPORARY TABLE helper(
date datetime not null
);
SET #start_date = DATEADD(year, -1 ,GETDATE())
WHILE #start_date < GETDATE()
BEGIN
INSERT INTO helper VALUES (#start_date)
SELECT #start_date = DATEADD(MONTH, 1, #start_date)
END
update 1
with this code I am still receiving a syntax error
CREATE TEMPORARY TABLE helper(
date datetime not null
);
insert into helper (date)
with recursive cte as (
select curdate() - interval 12 month as date
union all
select date + interval 1 month
from cte
where date < curdate()
)
select date from cte;
select * from helper;
error
"Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'insert into helper (date)\n with recursive cte as (\n select c' at line 4",
UPDATE 2
I have changed the syntax to this. I am still getting errors but it seems to be closer to mySql 5.7
`
CREATE TEMPORARY TABLE helper(
date datetime not null
);
CREATE PROCEDURE dowhile()
BEGIN
DECLARE date datetime DEFAULT SUBDATE(curdate(), interval 12 month);
WHILE date < curDate() DO
INSERT INTO helper VALUES(date);
SET date = date + interval 1 month;
END WHILE;
END;
DELIMITER;
CALL dowhile()
SELECT date_format(log.entry_stamp, '%Y-%M') AS 'date', COUNT(log.element_id) as count FROM dm_log log
INNER JOIN dm_element el
ON el.element_id = log.element_id
WHERE ? = el.dm_id
LEFT OUTER JOIN helper h
ON h.date = log.date
GROUP BY date
ORDER BY date;
DROP TEMPORARY TABLE helper;
`
Error
"Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CREATE PROCEDURE dowhile()\n BEGIN\n DECLARE date datetime DEFAU' at line 4",
You can use a recursive CTE instead of a WHILE loop:
insert into helper (date)
with recursive cte as (
select curdate() - interval 12 month as date
union all
select date + interval 1 month
from cte
where date < curdate()
)
select date from cte;
Here is a db<>fiddle.
Note that MySQL does not use getdate(). And doesn't support while loops outside of programming blocks. And doesn't have a three-argument form of dateadd(). It looks like you are confusing MySQL with SQL Server.
In earlier versions, you'll probably need to list the values explictly:
insert into helper (date)
select curdate() union all
select curdate() - interval 1 month union all
. . .;
I am new to SQL and learning at the moment. I am using MySQL installed on Ubuntu 18.04.
I have the following table:
CREATE TABLE IF NOT EXISTS product(name varchar(255) NOT NULL,
availability date NOT NULL);
I want to insert the following record in the table:
INSERT INTO `product` (`name`, `availability`)
VALUES ('Title 1', last Wednesday);
when executing I get the following error:
ERROR 1064 (42000) at line 21: You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the
right syntax to use near 'Wednesday)'
I understand that this format ('last Wednesday', 'next week',..) is not a standard date format. I wanted to know if it is possible to create user-defined types in MySQL to be able to process these bespoke date records.
So far what I have found on the web deals with dates that still contain more or less a standard date format, not as bespoke as those mentioned above (use cast, parse,..).
What you have are functions like NOW(), CURRENT_TIMESTAMP() and CURRENT_DATE(). For anything else like LAST_WEDNESDAY() you can write your own stored functions.
create function last_wednesday() returns date no sql
return current_date() - interval (weekday(current_date()) + 4)%7+1 day;
Or use the same expression inline in your queries.
Update
As asked by Strawberry - Here is something "more scalable":
create function human_to_date(str text, date date) returns date no sql
return case
when str = 'last monday' then date - interval (weekday(date) + 6-0)%7+1 day
when str = 'last tuesday' then date - interval (weekday(date) + 6-1)%7+1 day
when str = 'last wednesday' then date - interval (weekday(date) + 6-2)%7+1 day
when str = 'last thursday' then date - interval (weekday(date) + 6-3)%7+1 day
when str = 'last friday' then date - interval (weekday(date) + 6-4)%7+1 day
when str = 'last saturday' then date - interval (weekday(date) + 6-5)%7+1 day
when str = 'last sunday' then date - interval (weekday(date) + 6-6)%7+1 day
end
;
Use it as
select human_to_date('last wednesday', now())
or for any date as reference
select human_to_date('last sunday', '2019-10-01')
This will return the last sunday in this month (sept. 2019)
See demo
I've tried to remove code duplication, but ended with this:
delimiter //
create function human_to_date(str text, date date) returns date no sql
begin
declare day_of_week int default null;
if str rlike '^last (monday|tuesday|wednesday|thursday|friday|saturday|sunday)$' then
set day_of_week = case substring_index(str, ' ', -1)
when 'monday' then 0
when 'tuesday' then 1
when 'wednesday' then 2
when 'thursday' then 3
when 'friday' then 4
when 'saturday' then 5
when 'sunday' then 6
end;
return date - interval (weekday(date) + 6-day_of_week)%7+1 day;
end if;
return null;
end //
delimiter ;
db-fiddle
When it comes to dates, the natural language capabilities of some application languages is so good that I'd be tempted to handle the logic there instead.
For instance, here's some PHP:
<?php
echo(date(DATE_RFC850,strtotime( date('Y-m-01', strtotime('next month')).' last wednesday')));
?>
Today is Thursday 26th September. This echoes Wednesday, 25-Sep-19 00:00:00 BST
When storing in a db, you need information that make sense. a value "last Wednesday" for a product, does not give any reasonable availability info. For example, last Wednesday from which date? If someone asks for all available products on last Wednesday but 2 months later than today, what will a query looking for "last Wednesday" return? Definitely invalid results. So, if you want to store a date, then use a date/datetime datatype. You can add any custom description you want by using an additional column with varchar datatype, but i wouldn't use this as a primary info source.
Here is code to insert last Wednesday's date into your table:
INSERT INTO product(name, availability)
SELECT
'Title 1',
CASE WHEN WEEKDAY(CURDATE()) >= 2
THEN TIMESTAMPADD(DAY, 2 - WEEKDAY(CURDATE()), CURDATE())
ELSE TIMESTAMPADD(DAY, 2 - WEEKDAY(CURDATE()) - 7, CURDATE()) END;
Notes: It would be hard to pull this off using a select with a VALUES clause, unless perhaps we define a UDF, so I am using an INSERT INTO ... SELECT. The CASE expression checks if the current date is a Wednesday (2) through Sunday (6). If so, then we offset the current date by the difference, to shift it back to Wednesday. Similar logic applies for Monday (0) and Tuesday (1).
I have the following stored procedure to calculate running averages. However when called, it runs forever.
delimiter //
CREATE PROCEDURE run_avg(date_start DATE,date_end DATE,pol_id TINYINT,sta_id TINYINT)
NOT DETERMINISTIC
CONTAINS SQL
BEGIN
TRUNCATE average_table;
WHILE date_start <= date_end DO
INSERT INTO average_table(value_avg)
SELECT AVG(a.value) as val
FROM (SELECT value FROM obs_all_unik
WHERE start_time >=date_start
AND start_time < date_start + INTERVAL 8 HOUR
AND polutant_id=pol_id
AND station_id=sta_id) AS a;
SET date_start = date_start + INTERVAL 1 HOUR;
END WHILE;
END;//
delimiter ;
Any ideas?
INDEX(station_id, polution_id, start_date)
would help performance.
It seems wrong to insert just the averages into the table without also inserting these values: station_id, polution_id, start_date.
The endless loop was due to a wrong definition of date_start as DATE. Since I tried to increment by hours it was returning one and the same value. I changed it to TIMESTAMP and now the problem is gone.
I hasn't been writing any MySql stored procedures before, so I don't know them at all. I have one database table which has some records in it. The main column is dateTime - when this record was saved in the database.
I have wrote the MySql stored procedure to select every record from the specified date:
CREATE PROCEDURE getMessages(IN dateFrom DATETIME)
SELECT * FROM EsbMessage WHERE dateTime <= dateFrom;
And this is the call:
CALL getMessages('2012-10-04 13:11:09');
This works correctly, it returns me the records from the specified date.
What I need to do is:
If the record is over one week old, I need to update the other
column.
If the record is over one year old, I need to delete that record.
I can easily do this programmatically, but in this case I have to do this using stored procedure.
So I'am thinking of something like this:
CREATE PROCEDURE updateMessages(IN dateFrom DATETIME)
BEGIN
SELECT * FROM EsbMessage WHERE dateTime <= dateFrom;
#for each message
#if the message is over one week old but not over one year old:
UPDATE EsbMessage SET body = '';
#if message is over one year old:
DELETE FROM EsbMessage WHERE dateTime = #message.dateTime
END
But I don't know how to use for loop in stored procedure, and how to write if statements depending on my requirements and the other thing I don't now how to count the dates in MySql. For e.g. If I have the current date then I need to subtract the 365 days from the current date.
Could somebody help me with this issue?
You wouldn't need a loop, just have your conditions in the WHERE clause:
#if the message is over one week old but not over one year old:
UPDATE EsbMessage SET body = ''
WHERE dateTime >= DATE_SUB(NOW(),INTERVAL 1 WEEK) AND dateTime <= DATE_SUB(NOW(),INTERVAL 1 YEAR);
#if message is over one year old:
DELETE FROM EsbMessage WHERE dateTime >= DATE_SUB(NOW(),INTERVAL 1 YEAR);
How to loop and to use if clauses is described here: http://www.mysqltutorial.org/stored-procedures-loop.aspx
I would do it without loops:
CREATE PROCEDURE updateMessages(IN dateFrom DATETIME)
BEGIN
UPDATE EsbMessage SET body = '' where dateTime <= dateFrom -(86400*7); //86400 = 1 day
#if message is over one year old:
DELETE FROM EsbMessage where dateTime <= dateFrom -(86400*365);
END