I've got probably what is a very simple issue. However, I've researched extensively and haven't found a solution yet. Basically, I want to convert a modified string variable into the DATE format (specifically %Y).
I have a column variable called dob, which includes dates in the VARCHAR format. The values of these strings vary and can look like any of the following: 01 JAN 1900, ABT 1960, or Unknown. Nonetheless, the year is always the last four digits, so I'm grabbing the year by creating a substring. But I want to convert that substring into a YEAR format. My thought is that I need to use str_to_date to accomplish this.
This is my MySQL query:
SELECT dob, STR_TO_DATE(SUBSTRING(dob, -4), "%Y") as YEAR
FROM person_table;
Upon running it, I only get NULL values. Is there something I'm missing?
Here are my MySQL specs:
innodb_version: 5.7.20
protocol_version: 10
Thanks for your help!
Edit: Providing SQL Mode Information:
+---------------+-----------------------------------------------------------+
| Variable_name | Value |
+ --------------+-----------------------------------------------------------+
| sql_mode | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+---------------+------------------------------------------------------------
1 row in set (0.00 sec)
Upon running the query SELECT STR_TO_DATE('2009','%Y'); I get NULL and the following warning:
I get the following warning:
+---------+------+-----------------------------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------------------------+
| Warning | 1411 | Incorrect datetime value: '2009' for function str_to_date |
+---------+------+-----------------------------------------------------------+
1 row in set (0.00 sec)
str_to_date returns a date type, and only a year is an incomplete date. str_to_date will fill in the incomplete parts with zeroes unless you have no_zero_dates mode enabled. This is part of Strict SQL Mode which is the default in MySQL 8.0; it avoids the worst of MySQL's quirks.
Strict mode controls how MySQL handles invalid or missing values in data-change statements such as INSERT or UPDATE. A value can be invalid for several reasons. For example, it might have the wrong data type for the column, or it might be out of range
Without Strict SQL Mode, MySQL will turn "2009" into the invalid date 2009-00-00.
mysql> set sql_mode = '';
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT STR_TO_DATE('2009','%Y');
+--------------------------+
| STR_TO_DATE('2009','%Y') |
+--------------------------+
| 2009-00-00 |
+--------------------------+
1 row in set (0.00 sec)
With Strict SQL Mode it will not.
mysql> show variables like 'sql_mode';
+---------------+-----------------------------------------------------------------------------------------------------------------------+
| Variable_name | Value |
+---------------+-----------------------------------------------------------------------------------------------------------------------+
| sql_mode | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+---------------+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT STR_TO_DATE('2009','%Y');
+--------------------------+
| STR_TO_DATE('2009','%Y') |
+--------------------------+
| NULL |
+--------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+-----------------------------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------------------------+
| Warning | 1411 | Incorrect datetime value: '2009' for function str_to_date |
+---------+------+-----------------------------------------------------------+
1 row in set (0.00 sec)
To solve your problem, instead of trying to do a one-size-fits-all conversion, I would recommend trying several formats from most to least specific using coalesce. You will have to add the missing date parts as needed.
select coalesce(
str_to_date(dob, '%d %b %Y'),
str_to_date(concat(dob, '-01-01'), 'ABT %Y-%m-%d')
)
from person_table;
As this is very ugly to do, I also would recommend adding a proper date column and doing an update to convert from the messy string dates to proper dates. Then query the new column going forward.
alter table person_table add column dob_date date;
update person_table
set dob_date = coalesce(
str_to_date(dob, '%d %b %Y'),
str_to_date(concat(dob, '-01-01'), 'ABT %Y-%m-%d')
)
where dob_date is null;
You can then check for people with a null dob_date, examine their dob field, and adapt your conversion. Iterate as needed.
UPDATE
To add, yes, I need the year 2020 as opposed to the string. The reason being is because I need to compare the year values.
As strings they will not compare as you need. Strings compare character by character. '200' is greater than the string '1999'.
mysql> select '1999' < '2000';
+-----------------+
| '1999' < '2000' |
+-----------------+
| 1 |
+-----------------+
1 row in set (0.00 sec)
mysql> select '1999' < '200';
+----------------+
| '1999' < '200' |
+----------------+
| 1 |
+----------------+
1 row in set (0.00 sec)
You need to cast them to signed integers.
mysql> select cast("1999" as signed) < cast('2000' as signed);
+-------------------------------------------------+
| cast("1999" as signed) < cast('2000' as signed) |
+-------------------------------------------------+
| 1 |
+-------------------------------------------------+
1 row in set (0.00 sec)
mysql> select cast("1999" as signed) < cast('200' as signed);
+------------------------------------------------+
| cast("1999" as signed) < cast('200' as signed) |
+------------------------------------------------+
| 0 |
+------------------------------------------------+
1 row in set (0.01 sec)
So your query would be...
select dob, cast(substring(dob, -4) as signed) as year
from person_table;
Related
I have a stored MySQL procedure called quote_of_the_day that is supposed to select a different row from the quotes table each day, using the date as the seed value. When I test the code as a query, everything appears to run fine, BUT when I call the function in my php code (query is CALL quote_of_the_day()) it only ever selects one of three different quotes, despite the fact that there are 23 quotes in the table.
The create systax for quote_of_the_day is:
DELIMITER ;;
CREATE DEFINER=`root`#`%` PROCEDURE `quote_of_the_day`()
BEGIN
# save the number of rows into a variable
SET #num_rows = (SELECT COUNT(*) FROM `quotes`);
# calculate a random number no greater than the number of rows in the table
SET #rand_num = (SELECT FLOOR(RAND(CURDATE())*(#num_rows+1)));
# select random quote from the list of quotes (seed value is the current day)
SELECT quote FROM quotes WHERE id = #rand_num;
# increment the quoted column to keep track of which quotes are selected
UPDATE quotes SET quoted = quoted + 1 WHERE id = #rand_num AND last_used <> CURDATE();
UPDATE quotes SET last_used = CURDATE() WHERE id = #rand_num;
END;;
DELIMITER ;
Where am I going wrong?
The comments above are teasing you with the answer.
Read the manual on MySQL's RAND() function:
...for equal argument values, RAND(N) returns the same value each time, and thus produces a repeatable sequence of column values. In the following example, the sequence of values produced by RAND(3) is the same both places it occurs.
Since CURDATE() returns a constant value every time you call it within the same day, passing it to RAND() makes RAND() return the same value every time you call it.
Demo:
mysql> select rand(curdate());
+------------------+
| rand(curdate()) |
+------------------+
| 0.49455075570806 |
+------------------+
1 row in set (0.00 sec)
mysql> select rand(curdate());
+------------------+
| rand(curdate()) |
+------------------+
| 0.49455075570806 |
+------------------+
1 row in set (0.00 sec)
mysql> select rand(curdate());
+------------------+
| rand(curdate()) |
+------------------+
| 0.49455075570806 |
+------------------+
1 row in set (0.00 sec)
mysql> select rand(curdate());
+------------------+
| rand(curdate()) |
+------------------+
| 0.49455075570806 |
+------------------+
1 row in set (0.00 sec)
This will continue until my clock says it's a new day.
You don't actually need to pass a value to seed RAND(). It gets a seed when the MySQL server starts, and continues to generate random values.
Don't use the seed argument when you want a series of new random values, use the seed argument when you want a reproducible series of random values, for example if you're running automated tests.
Re your comment:
The argument to RAND() is an integer, but dates like '2021-11-07' are returned as a string. In a numeric context, the integer value of '2021-11-07' is 2021. Any non-numeric characters are stripped off before passing it as the argument to RAND().
mysql> select rand('2021-11-04');
+--------------------+
| rand('2021-11-04') |
+--------------------+
| 0.7752841103591808 |
+--------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select rand('2021-11-05');
+--------------------+
| rand('2021-11-05') |
+--------------------+
| 0.7752841103591808 |
+--------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select rand('2021-11-06');
+--------------------+
| rand('2021-11-06') |
+--------------------+
| 0.7752841103591808 |
+--------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select rand('2021-11-07');
+--------------------+
| rand('2021-11-07') |
+--------------------+
| 0.7752841103591808 |
+--------------------+
1 row in set, 1 warning (0.00 sec)
You can see that it's doing this:
mysql> show warnings;
+---------+------+-------------------------------------------------+
| Level | Code | Message |
+---------+------+-------------------------------------------------+
| Warning | 1292 | Truncated incorrect INTEGER value: '2021-11-07' |
+---------+------+-------------------------------------------------+
All these dates are seeding the randomizer as if you had passed only '2021' or 2021 as the argument:
mysql> select rand('2021');
+--------------------+
| rand('2021') |
+--------------------+
| 0.7752841103591808 |
+--------------------+
1 row in set (0.00 sec)
mysql> select rand(2021);
+--------------------+
| rand(2021) |
+--------------------+
| 0.7752841103591808 |
+--------------------+
1 row in set (0.00 sec)
Your seed is constant to for the year of the date because when MySQL converts strings to dates it uses the first string of digits it finds in the string, which is currently 2021.
Use TO_DAYS() Seed RAND() with the number of days since year 0:
RAND(TO_DAYS(CURDATE()))
Every day will forever pass a different, but constant for the current date, value to rand()
I'm trying to convert string to date in mysql. Inspite of giving correct string and format specifier, mysql is giving NULL output with warning message as Incorrect datetime value: 'XXXXX' for function str_to_date although it is working for full date string.
mysql logs
mysql> SELECT STR_TO_DATE('2018/06', '%Y/%m');
+---------------------------------+
| STR_TO_DATE('2018/06', '%Y/%m') |
+---------------------------------+
| NULL |
+---------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT STR_TO_DATE('2018/06/01', '%Y/%m/%d'); +---------------------------------------+
| STR_TO_DATE('2018/06/01', '%Y/%m/%d') |
+---------------------------------------+
| 2018-06-01 |
+---------------------------------------+
1 row in set (0.00 sec)
debug info
MySQL version - 5.7.21 MySQL Community Server (GPL)
mysql> show warnings;
+---------+------+--------------------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------------------+
| Warning | 1411 | Incorrect datetime value: '2018/06' for function str_to_date |
+---------+------+--------------------------------------------------------------+
1 row in set (0.00 sec)
Requirement
I want to parse month and year from above date string.
For fulfilling my requirement, I'm using following workaround -
mysql> SELECT YEAR(STR_TO_DATE(CONCAT('2018/06', '/01'), '%Y/%m/%d'));
+---------------------------------------------------------+
| YEAR(STR_TO_DATE(CONCAT('2018/06', '/01'), '%Y/%m/%d')) |
+---------------------------------------------------------+
| 2018 |
+---------------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT MONTH(STR_TO_DATE(CONCAT('2018/06', '/01'), '%Y/%m/%d'));
+----------------------------------------------------------+
| MONTH(STR_TO_DATE(CONCAT('2018/06', '/01'), '%Y/%m/%d')) |
+----------------------------------------------------------+
| 6 |
+----------------------------------------------------------+
1 row in set (0.00 sec)
You can use DATE_FORMAT function in mysql
SELECT DATE_FORMAT('2018/06/01', '%Y-%m')
NB: The date string should be in any valid MYSQL DATE FORMAT(Year month day)
If your date string is always in 'yyyy/mm' format
instead of using date function you can use substring OR substring_index function in MYSQL.
select substring("2018/06",1,4) AS year, substring("2018/06",6,2) as month
OR
select substring_index("2018/06","/",1) AS year, substring_index("2018/06","/",-1) as month
I get the following error when I try to insert the datetime value in a timestamp field in my MySQL table. I am trying to do this by running a Python code.
_mysql_exceptions.OperationalError: (1292, "Incorrect datetime value: '2018-03-26 10:59:27+00:00' for column 'timestamp' at row 1")
Is there a workaround or a solution to this error ?
2018-03-26 10:59:27+00:00
This is a valid iso-8601 datetime value, but it is not a valid MySQL datetime literal. On that point, the developer is incorrect.
The documentation explains what ALLOW_INVALID_DATES does:
Check only that the month is in the range from 1 to 12 and the day is in the range from 1 to 31.
In other words, 2018-03-26 would be a permissible date if allow_invalid_dates is set. This option does not do anything when the date or datetime isn't even in a valid format for MySQL.
The +00:00 is the timezone offset from UTC. In this case, the time expressed is in UTC, so the offset is zero hours, zero minutes.
Your workaround would be to remove the STRICT_TRANS_TABLES from the sql_mode that is a default in the config file created during the MySQL 5.6 installation process... you need to carefully consider the implications of changing this, but it does allow the data to go in.
mysql> select ##sql_mode;
+--------------------------------------------+
| ##sql_mode |
+--------------------------------------------+
| STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION |
+--------------------------------------------+
1 row in set (0.00 sec)
mysql> insert into datetimetest(dt) values ('2018-03-26 10:59:27+00:00');
ERROR 1292 (22007): Incorrect datetime value: '2018-03-26 10:59:27+00:00' for column 'dt' at row 1
-- remove STRICT_TRANS_TABLES -- note that executing this only removes it for your
-- current session -- it does not make a server-wide config change
mysql> set ##sql_mode='no_engine_substitution';
Query OK, 0 rows affected (0.00 sec)
mysql> select ##sql_mode;
+------------------------+
| ##sql_mode |
+------------------------+
| NO_ENGINE_SUBSTITUTION |
+------------------------+
1 row in set (0.00 sec)
-- now MySQL will accept the invalid value, with a warning
mysql> insert into datetimetest(dt) values ('2018-03-26 10:59:27+00:00');
Query OK, 1 row affected, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+-----------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------+
| Warning | 1265 | Data truncated for column 'dt' at row 1 |
+---------+------+-----------------------------------------+
1 row in set (0.00 sec)
-- the value did get inserted, but the time zone information was lost:
mysql> select * from datetimetest;
+----+---------------------+
| id | dt |
+----+---------------------+
| 1 | 2013-08-26 12:00:00 |
+----+---------------------+
1 row in set (0.00 sec)
select * ,str_to_date(date,'%Y-%m-%d')
from numberofmortageloans;
Screenshot of the code not working
I'm sure I am missing something but let me know
Your date values are invalid, like '2005-03-00'.
If STR_TO_DATE() can't evaluate the date as a legitimate date, it returns NULL.
mysql> select str_to_date('2005-03-00', '%Y-%m-%d');
+---------------------------------------+
| str_to_date('2005-03-00', '%Y-%m-%d') |
+---------------------------------------+
| NULL |
+---------------------------------------+
If you try it with a real date, STR_TO_DATE() works fine.
mysql> select str_to_date('2005-03-01', '%Y-%m-%d');
+---------------------------------------+
| str_to_date('2005-03-01', '%Y-%m-%d') |
+---------------------------------------+
| 2005-03-01 |
+---------------------------------------+
So, garbage in, garbage out.
The manual (https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_str-to-date) says in part:
If the NO_ZERO_DATE or NO_ZERO_IN_DATE SQL mode is enabled, zero dates or part of dates are disallowed. In that case, STR_TO_DATE() returns NULL and generates a warning:
mysql> set sql_mode='';
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> select str_to_date('2005-03-00', '%Y-%m-%d');
+---------------------------------------+
| str_to_date('2005-03-00', '%Y-%m-%d') |
+---------------------------------------+
| 2005-03-00 |
+---------------------------------------+
So you could change your SQL_MODE. But I would recommend you fix your data instead.
I'm, trying to calculate the number of days between two dates using ANSI SQL standard. But I'm missing something as this statement returns NULL in MySQL.
SELECT EXTRACT(DAY FROM DATE('2009-01-25') - DATE('2009-01-01')) AS day_diff;
I'm aware of the MySQL DATEDIFF function, but I'm curious why this code isn't working.
What am I missing?
Is this what you meant to do?
mysql> SELECT EXTRACT(DAY FROM DATE('2009-01-25')) -
EXTRACT(DAY FROM DATE('2009-01-01')) AS day_diff;
+----------+
| day_diff |
+----------+
| 24 |
+----------+
1 row in set (0.00 sec)
UPDATE:
If you want this to work for dates in different months (or even different years), then you can use the MySQL DATEDIFF() function.
Examples:
mysql> select datediff('2009-04-25','2009-01-01');
+-------------------------------------+
| datediff('2009-04-25','2009-01-01') |
+-------------------------------------+
| 114 |
+-------------------------------------+
1 row in set (0.00 sec)
mysql> select datediff('2010-04-25','2009-01-01');
+-------------------------------------+
| datediff('2010-04-25','2009-01-01') |
+-------------------------------------+
| 479 |
+-------------------------------------+
1 row in set (0.00 sec)