I have a column in my database called time. the type of this column is timestamp and Default value is CURRENT_TIMESTAMP
But after some inserts, in phpMyAdmin it shows the value as datetime, e.g. 2019-05-05 04:24:45 and even the Timezone is shown there and can be changed!
I thought MySQL's timestamp is 4 bytes (compared to 8 bytes of datetime) and doesn't store timezone and data is same as INT(10) such as: 1557094115 (seconds passed since 1970 or something like that)
Can any one please explain this, is it a bug or something?
MySQL version 5.7.25
Edit 1 (Screenshots):
It is a TIMESTAMP column, with default value of CURRENT_TIMESTAMP
As you see it is shown as DATETIME and I cannot compare it with integer value of unix_timestamp... also we can change TimeZone to any value (I thought timestamp doesn't store timezone...)
Edit 2:
If (based on one answer) MySQL stores it as an integer internally, then why can't I compare it with integers? (the following query won't work)
DELETE FROM `table` WHERE time < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL :days DAY))
SQLSTATE[22007]: Invalid datetime format: 1292 Incorrect datetime value: '1555980012' for column 'time' at row 1
I also tried it in Sequel Pro and MySQLWorkbench with same results
If you need to see 1557094115, then apply the function UNIX_TIMESTAMP() to the TIMESTAMP or DATETIME column. It's inverse is FROM_UNIXTIME().
mysql> SELECT UNIX_TIMESTAMP("2019-05-05 04:24:45"), FROM_UNIXTIME(1557055485);
+---------------------------------------+---------------------------+
| UNIX_TIMESTAMP("2019-05-05 04:24:45") | FROM_UNIXTIME(1557055485) |
+---------------------------------------+---------------------------+
| 1557055485 | 2019-05-05 04:24:45 |
+---------------------------------------+---------------------------+
More
The internal storage for TIMESTAMP is 1557055485 in UTC; the timezone is added/removed as it is fetched/stored.
The internal storage for DATETIME is (logically, but not actually) the string "2019-05-05 04:24:45" with no hint of timezone. (Actually, it is packed into 5 bytes in some fashion.)
Without any conversion function, fetching TIMESTAMP and DATETIME look the same:
CREATE TABLE `dtts` (
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`dt` datetime DEFAULT NULL,
`just_date` date NOT NULL,
`di` int(11) DEFAULT NULL,
`ts_int` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci
1 row in set (0.00 sec)
mysql> select * from dtts
-> ;
+---------------------+---------------------+------------+------------+------------+
| ts | dt | just_date | di | ts_int |
+---------------------+---------------------+------------+------------+------------+
| 2017-06-26 17:52:53 | 2011-06-08 20:45:55 | 2011-06-08 | 20110608 | 1465404577 |
| 2017-06-26 17:52:53 | 2013-03-10 02:35:47 | 2013-03-10 | 20130310 | 1465404577 |
Adding NOW() to both, then SELECTing:
mysql> INSERT INTO dtts (ts, dt) VALUES (NOW(), NOW());
Query OK, 1 row affected, 1 warning (0.00 sec)
| 2019-05-08 14:14:07 | 2019-05-08 14:14:07 | 0000-00-00 | NULL | NULL |
+---------------------+---------------------+------------+------------+------------+
DateTime doesn't store timezone information (it's value only), while MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and convert from UTC to the current time zone for retrieval. What you are seeing from PhpMyAdmin is the retrieved value, not stored value.
Since MySQL 5.6.4, the storage of DateTime has been improved from 8 bytes to 5 bytes (+ fractional seconds storage) Reference
Since MySQL 5.6.4, the DATETIME field requires 5 bytes + 3 bytes fractional. The TIMESTAMP type requires 4 bytes + 3 bytes fractional. Neither of these data types store time zone information. However, both MySQL and phpMyAdmin display TIMESTAMP fields according to the timezone of the database server. You can retrieve the database server's timezone info with the following statements:
SELECT ##global.time_zone, ##session.time_zone;
SELECT EXTRACT(HOUR FROM (TIMEDIFF(NOW(), UTC_TIMESTAMP))) AS `timezone`
If you would like phpMyAdmin to display a different timezone from the database server, you can set the SessionTimeZone property inside of phpMyAdmin's config.inc.php file.
Related
We are migrating our application from MySQL 5.5 to 5.7. As the default value 0000-00-00 is not allowed anymore for date fields in MySQL 5.7 in strict mode, I would like to change the default value to NULL.
The concerned fields are defined as follows:
+------------------+----------------------+------+-----+------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+----------------------+------+-----+------------+----------------+
| event_start_date | date | YES | | 0000-00-00 | |
| event_end_date | date | YES | | 0000-00-00 | |
+------------------+----------------------+------+-----+------------+----------------+
When I try to execute the following ALTER query:
ALTER TABLE events CHANGE event_start_date event_start_date date DEFAULT NULL;
ALTER TABLE events CHANGE event_end_date event_end_date date DEFAULT NULL;
I get the following error:
Invalid default value for 'event_end_date'
I know it would be possible to disable strict mode, but that is not the solution I am looking for. Strangely enough the same query worked for an other table.
Anyone has an idea what is going wrong?
The error happens already in your query on the first line. There you are trying to change the column event_start_date, the error message however is for column event_end_date. You need to change both columns with a single query in order to avoid this error:
ALTER TABLE events CHANGE event_start_date event_start_date date DEFAULT NULL, CHANGE event_end_date event_end_date date DEFAULT NULL;
It probably worked with your other table because you only had one column of type date.
This is the new strict mode in MySQL 5.7. The default SQL_MODE in MySQL 5.7 is:
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
The best way is to change the schema as #cansik suggested.
You can also remove NO_ZERO_IN_DATE,NO_ZERO_DATE from sql_mode (not recommended but can be temporary workaround)
set global sql_mode="... choose which modes you need ... "
Our database uses '0000-00-00 00:00:00' as the default value for many datetime and timestamp fields. MySQL has apparently decided that they only want us to use a valid date or null for these types of fields.
However, the '0000-00-00 00:00:00' values used to be acceptable and our code checks for this value. When I setup a new server, I edit the /etc/mysql/mysql.conf.d/mysqld.cnf file and add one line to the [mysqld] section.
sql_mode=NO_ENGINE_SUBSTITUTION
Today I have attempted to setup a new server. I added the sql_mode=NO_ENGINE_SUBSTITUTION to the MySQL configuration and restarted the mysql service. However, this new server only gets errors.
UPDATE example_table SET active = 1 WHERE example_table_id = 1;
ERROR 1292 (22007): Incorrect datetime value: '0000-00-00 00:00:00' for column 'my_date_field' at row 1
I could of course update the database to have NULL values or a "valid" default date such as '1970-01-01 00:00:01', but this would break existing code that checks the data for '0000-00-00 00:00:00'.
Additional Example Information:
Server type: MySQL
Server version: 5.7.22-0ubuntu0.16.04.1 - (Ubuntu)
Protocol version: 10
innodb_version: 5.7.22
SELECT my_date_field FROM example_table WHERE active = 1;
+---------------------+
| my_date_field |
+---------------------+
| 0000-00-00 00:00:00 |
+---------------------+
DESC example_table;
+------------------------------------+--------------+------+-----+---------------------+
| Field | Type | Null | Key | Default |
+------------------------------------+--------------+------+-----+---------------------+
| my_date_field | datetime | NO | | 0000-00-00 00:00:00 |
| active | tinyint(2) | NO | | 1 |
+------------------------------------+--------------+------+-----+---------------------+
I've got other machines on the same version of MySQL working with just sql_mode=NO_ENGINE_SUBSTITUTION and I'm almost to the point of just imaging one of those for this new machine, until such time as the code is updated to look for a valid date or null instead of '0000-00-00 00:00:00'.
The NO_ENGINE_SUBSTITUTION SQL mode doesn't have anything to do with dates. It only did the trick for you because by setting it you also removed the server's default mode, which in modern versions is something on the line of TRADITIONAL, which is a combination mode that, among others, includes NO_ZERO_IN_DATE and NO_ZERO_DATE.
You possibly just want to set a legacy mode for this application at session level, e.g.:
SET ##SESSION.sql_mode = '';
I've made a test. Here is a innodb table:
CREATE TABLE `test_UNIX_TIMESTAMP` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`datefrom` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
Here is an insert:
insert into test_UNIX_TIMESTAMP (datefrom) values
(UNIX_TIMESTAMP())
,(UNIX_TIMESTAMP())
,(UNIX_TIMESTAMP())
....many many
,(UNIX_TIMESTAMP());
Here is a query to check is it unique timestamps or not:
select distinct(datefrom) from test_UNIX_TIMESTAMP;
The results are:
After Affected rows: 106 400, we have 1 unique timestamp value in the table.
After Affected rows: 1 170 400 , we still have 1 unique timestamp value in the table.
MySQL server - it's remote SQL server (xeon 1270v3 with 32 ram, mysql have 4Gb cache).
Is "UNIX_TIMESTAMP()" value fixed for large insert queries? Or it's fixed for session? Or i'm just lucky?
The MySQL documentation isn't clear on this. However, it does draw a distinction between CURRENT_TIMESTAMP() (or NOW()) and SYSDATE() — the former is evaluated once per query, while the latter is evaluated at every invocation (and is presumably a bit slower).
You can demonstrate this for yourself by calling both either side of a SLEEP instruction:
mysql> select NOW() "a", SYSDATE() "b", SLEEP(2) "_", NOW() "c", SYSDATE() "d";
+---------------------+---------------------+---+---------------------+---------------------+
| a | b | _ | c | d |
+---------------------+---------------------+---+---------------------+---------------------+
| 2015-11-04 11:18:55 | 2015-11-04 11:18:55 | 0 | 2015-11-04 11:18:55 | 2015-11-04 11:18:57 |
+---------------------+---------------------+---+---------------------+---------------------+
1 row in set (2.01 sec)
As you can see, SYSDATE() increases during the query, while NOW() remains constant. It turns out that UNIX_TIMESTAMP() is also evaluated once per query:
mysql> select UNIX_TIMESTAMP(), SLEEP(2), UNIX_TIMESTAMP();
+------------------+----------+------------------+
| UNIX_TIMESTAMP() | SLEEP(2) | UNIX_TIMESTAMP() |
+------------------+----------+------------------+
| 1446635945 | 0 | 1446635945 |
+------------------+----------+------------------+
1 row in set (2.00 sec)
I guess it's the same as for the NOW() function:
NOW() returns a constant time that indicates the time at which the statement began to execute. (Within a stored function or trigger, NOW() returns the time at which the function or triggering statement began to execute.) This differs from the behavior for SYSDATE(), which returns the exact time at which it executes.
Unfortunately there's no specific documentation for other functions regarding this case. If you want to make sure, to always have the same value, just NOW() with UNIX_TIMESTAMP(), like
SELECT UNIX_TIMESTAMP(NOW());
I'm trying to store a 12/24hr (ie; 00:00) clock time in a MySQL database. At the moment I am using the time datatype. This works ok but it insists on adding the seconds to the column. So you enter 09:20 and it is stored as 09:20:00. Is there any way I can limit it in MySQL to just 00:00?
That doesn't look possible. The TIME data type is defined to represent the time of the day (or elapsed time) with a 1 second resolution. However, you can always use the DATE_FORMAT() function to format your field as HH:MM in a SELECT query:
SELECT DATE_FORMAT(NOW(), '%k:%i');
+-----------------------------+
| DATE_FORMAT(NOW(), '%k:%i') |
+-----------------------------+
| 4:09 |
+-----------------------------+
1 row in set (0.00 sec)
SELECT DATE_FORMAT(NOW(), '%H:%i');
+-----------------------------+
| DATE_FORMAT(NOW(), '%H:%i') |
+-----------------------------+
| 04:09 |
+-----------------------------+
1 row in set (0.00 sec)
The TIME column type does not accept any parameter or modifier to define range or precision. You can, however, omit seconds on insert if you are careful:
Be careful about assigning abbreviated values to a TIME column. MySQL
interprets abbreviated TIME values with colons as time of the day.
That is, '11:12' means '11:12:00', not '00:11:12'. MySQL interprets
abbreviated values without colons using the assumption that the two
rightmost digits represent seconds (that is, as elapsed time rather
than as time of day). For example, you might think of '1112' and 1112
as meaning '11:12:00' (12 minutes after 11 o'clock), but MySQL
interprets them as '00:11:12' (11 minutes, 12 seconds). Similarly,
'12' and 12 are interpreted as '00:00:12'.
CREATE TABLE example (
example_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
elapsed TIME NOT NULL,
PRIMARY KEY (example_id)
);
INSERT INTO example (elapsed) VALUES ('123:45:00'), ('123:45');
SELECT * FROM example;
+------------+-----------+
| example_id | elapsed |
+------------+-----------+
| 1 | 123:45:00 |
| 2 | 123:45:00 |
+------------+-----------+
... and you can remove them on read (if necessary) by applying a proper TIME_FORMAT(), noting that:
If the time value contains an hour part that is greater than 23, the
%H and %k hour format specifiers produce a value larger than the usual
range of 0..23. The other hour format specifiers produce the hour
value modulo 12.
INSERT INTO example (elapsed) VALUES ('2:00');
SELECT example_id, TIME_FORMAT(elapsed, '%k:%i') AS elapsed
FROM example;
+------------+---------+
| example_id | elapsed |
+------------+---------+
| 1 | 123:45 |
| 2 | 123:45 |
| 3 | 2:00 |
+------------+---------+
Since MySQL/5.7.5 you can also use a generated column to get a display value automatically:
-- Completely untested, I don't have 5.7 yet
CREATE TABLE example (
example_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
elapsed TIME NOT NULL,
-- Size to accomodate for '-838:59:59'
elapsed_display VARCHAR(10) AS (TIME_FORMAT(elapsed, '%k:%i')) VIRTUAL NOT NULL,
PRIMARY KEY (example_id)
);
I could be wrong here, but it looks like there's conflicting standards here.
MySQL treats a stored datetime of "0000-00-00 00:00:00" as being equivalent to NULL.
(update - only, it seems, if the datetime is defined as NOT NULL)
But Rose::DB::Object uses DateTime for MySQL DATETIME fields, and trying to set a null DATETIME from "0000-00-00" throws an exception in the DateTime module. ie, I can't create a DateTime object with year 0, month 0, day 0, because this throws an exception in the DateTime module.
I checked in Rose::DB::Object::Metadata::Column::Datetime, and can't see a way of explicitly handling a NULL DateTime when creating an entry or when retrieving.
Am I missing something?
ie, can Rose::DB::Object handle NULL datetime (MySQL) fields even though DateTime (Perl module) can't.
Sample code:
#!/usr/bin/perl
use strict;
use warnings;
use lib 'lib';
use RoseDB::dt_test;
my $dt_entry = RoseDB::dt_test->new();
$dt_entry->date_time_field('0000-00-00');
$dt_entry->save;
1;
__END__
# definition of table as stored in DB
mysql> show create table dt_test \G
*************************** 1. row ***************************
Table: dt_test
Create Table: CREATE TABLE `dt_test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`date_time_field` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
with the RoseDB::dt_test module being:
package RoseDB::dt_test;
use strict;
use warnings;
# this module builds up our DB connection and initializes the connection through:
# __PACKAGE__->register_db
use RoseDB;
use base qw(Rose::DB::Object);
__PACKAGE__->meta->setup (
table => 'dt_test',
columns =>
[
id => { type => 'int', primary_key => 1 },
date_time_field => { type => 'datetime' },
],
);
sub init_db { RoseDB->get_dbh }
1;
When I run it, I get the error "Invalid datetime: '0000-00-00' at tmp.pl line 8"
When I change the date to "2010-01-01", it works as expected:
mysql> select * from dt_test\G
*************************** 1. row ***************************
id: 1
date_time_field: 2010-01-01 00:00:00
I finally managed to recreate the NULL MySQL query example!
mysql> create table dt_test(dt_test_field datetime not null);
Query OK, 0 rows affected (0.05 sec)
mysql> insert into dt_test values(null);
ERROR 1048 (23000): Column 'dt_test_field' cannot be null
mysql> insert into dt_test values('0000-00-00');
Query OK, 1 row affected (0.00 sec)
mysql> select * from dt_test;
+---------------------+
| dt_test_field |
+---------------------+
| 0000-00-00 00:00:00 |
+---------------------+
1 row in set (0.00 sec)
mysql> select * from dt_test where dt_test_field is null;
+---------------------+
| dt_test_field |
+---------------------+
| 0000-00-00 00:00:00 |
+---------------------+
1 row in set (0.00 sec)
Looks like the table definitions where the datetimes are defined with "NOT NULL" and then trying to use the MySQL "fake null" is the issue. I'm too tired to play with this now, but I'll see what happens when I change the table structure in the morning.
You should be able to set a datetime column to the literal desired 0000-00-00 00:00:00 value and save it to the database:
$o->mycolumn('0000-00-00 00:00:00');
$o->save;
Such "all zero" values will not be converted to DateTime objects by Rose::DB::Object, but rather will remain as literal strings. There is no semantic DateTime object equivalent for MySQL's 0000-00-00 00:00:00 datetime strings.
Note: 0000-00-00 is a valid date value, but a datetime (or timestamp) value must include the time: 0000-00-00 00:00:00
To set a column to null, pass undef as the column value:
$o->mycolumn(undef);
Of course, if the column definition in the database includes a NOT NULL constraint, the save() won't work.
MySQL allows date types to be incomplete (lacking year, month, and or day). '0000-00-00' is a valid, non-NULL MySQL date. Why do you think it matches IS NULL?
$ echo "select date('0000-00-00') is null" | mysql
date('0000-00-00') is null
0
For comparison:
$ echo "select date('0000-00-32') is null" | mysql
date('0000-00-32') is null
1