MySQL `timestamp` to be updated even with same column value - mysql

I am using timestamp columns to one of my table, and using the auto update functionality. Here is my table schema:
mysql> desc user_rides;
+------------+--------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+-------------------+-----------------------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | MUL | NULL |
| ride_cnt | int(11) | YES | | NULL | |
| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |
| updated_at | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+------------+--------------+------+-----+-------------------+-----------------------------+
5 rows in set (0.02 sec)
What I'm expecting is that,
created_at column to be initialize with the time, the row gets created and
updated_at column to be same as created_at and also updated when any of the columns(basically ride_cnt) get updated.
This works great.
But what I am also expecting is that the updated_at to be updated even if ride_cnt has the same value. So that I can keep a track of when was the last time the row's value fetched and can be ignored for further run.
For example:
The rows with ride_cnt = 0 to be updated with the latest time we ran the update. So that the rows can be ignored for quite sometime to be reinitialize.
Is there any way we can achieve this without passing in timestamp manually?
Edit:
Here what's happening,
mysql> insert into user_ride set user_id=7445, user_ride=0;
Query OK, 1 row affected (0.01 sec)
mysql> insert into user_ride set user_id=7009, user_ride=2;
Query OK, 1 row affected (0.00 sec)
mysql> select * from user_ride;
+----+---------+-----------+---------------------+---------------------+
| id | user_id | user_ride | created_at | updated_at |
+----+---------+-----------+---------------------+---------------------+
| 1 | 7445 | 0 | 2017-06-13 10:44:05 | 2017-06-13 10:44:05 |
| 2 | 7009 | 2 | 2017-06-13 10:44:18 | 2017-06-13 10:44:18 |
+----+---------+-----------+---------------------+---------------------+
2 rows in set (0.00 sec)
mysql> update user_ride set user_ride=0 where id=1;
Query OK, 0 rows affected (0.01 sec)
Rows matched: 1 Changed: 0 Warnings: 0
mysql> select * from user_ride;
+----+---------+-----------+---------------------+---------------------+
| id | user_id | user_ride | created_at | updated_at |
+----+---------+-----------+---------------------+---------------------+
| 1 | 7445 | 0 | 2017-06-13 10:44:05 | 2017-06-13 10:44:05 |
| 2 | 7009 | 2 | 2017-06-13 10:44:18 | 2017-06-13 10:44:18 |
+----+---------+-----------+---------------------+---------------------+
2 rows in set (0.00 sec)
mysql> update user_ride set user_ride=1 where id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from user_ride;
+----+---------+-----------+---------------------+---------------------+
| id | user_id | user_ride | created_at | updated_at |
+----+---------+-----------+---------------------+---------------------+
| 1 | 7445 | 1 | 2017-06-13 10:44:05 | 2017-06-13 10:45:26 |
| 2 | 7009 | 2 | 2017-06-13 10:44:18 | 2017-06-13 10:44:18 |
+----+---------+-----------+---------------------+---------------------+
2 rows in set (0.00 sec)

I wish to call attention to your first update in the scenario you described above:
mysql> update user_ride set user_ride=0 where id=1;
Query OK, 0 rows affected (0.01 sec)
Rows matched: 1 Changed: 0 Warnings: 0
We can see that there was a matching row, but no update actually took place. The reason for this is the update would have resulted in no data actually changing. Hence, the ON UPDATE clause of the updated_at timestamp never kicked in. I can offer two workarounds for your problem. The first, probably the most performant, would be to just manually set the updated_at column to the current timestamp during the update. Hence, from the example above you would use this instead:
update user_ride set user_ride = 0, updated_at = CURRENT_TIMESTAMP where id=1;
This should trigger an actual update of the row, because the timestamp has changed since it was last updated.
Another workaround would be to find a way to ensure that each update will always change some of the data in the given record. Then, the ON UPDATE clause would always be applied.
This may seem like a limitation, but I guess MySQL does not consider a change as having happened to a record if the underlying data itself did not change.
This question is sort of a duplicate of this one:
How to Force UPDATE of MySQL Record When Nothing Is Changing
However, since SO's coverage of this problem is so thin, I thought this answer might be useful to anyone else encountering the same problems.

Add a trigger.
CREATE TRIGGER user_ride_bu
BEFORE UPDATE ON user_ride
FOR EACH ROW
SET NEW.updated_at = NOW();
(For those wondering where the DELIMITER directives are, they aren't needed when a trigger has a single, simple statement).

Related

MySQL statement with invalid data in WHERE clause returns 0 rows instead of failing

Hopefully this is a simple question. I have the following table:
+---------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+---------+------+-----+---------+-------+
| KEY_COL | int(11) | NO | PRI | NULL | |
| COL2 | int(11) | YES | | NULL | |
+---------+---------+------+-----+---------+-------+
2 rows in set (0.01 sec)
When I do an update on the table using invalid values in the where clause for KEY_COL, it completes successfully and returns 0 rows affected:
mysql> update LGSRV_TAB_DOC set COL2='9' where KEY_COL='A';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
I have my sql_mode set to STRICT_ALL_TABLES, so I was expecting the update to fail since I'm supplying an invalid value for KEY_COL ('A' instead of a numeric). Is there any way I can change this behavior so that the update fails with an error? I'm running version 8.0.11 on RHEL 6.9.

How does MySQL index not speed up update query?

I have a table located in RAM and doing some performance tests.
Let's consider a sample query, adding explain sentences along with results
mysql> explain update users_ram set balance = balance + speed where sub = 1;
+----+-------------+-----------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | UPDATE | users_ram | NULL | ALL | NULL | NULL | NULL | NULL | 2333333 | 100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set (0.00 sec)
mysql> update users_ram set balance = balance + speed where sub = 1;
Query OK, 1166970 rows affected (0.37 sec)
Rows matched: 1166970 Changed: 1166970 Warnings: 0
As you can see, it takes 0.37 sec without index. Then I'm creating an index on the sub column, which is an int column with just two possible values of 0 and 1, and surprisingly nothing changes
mysql> create index sub on users_ram (sub);
Query OK, 2333333 rows affected (2.04 sec)
Records: 2333333 Duplicates: 0 Warnings: 0
mysql> show index from lords.users_ram;
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| users_ram | 0 | user | 1 | user | NULL | 2333333 | NULL | NULL | YES | HASH | | |
| users_ram | 1 | sub | 1 | sub | NULL | 2 | NULL | NULL | | HASH | | |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)
mysql> explain update users_ram set balance = balance + speed where sub = 1;
+----+-------------+-----------+------------+-------+---------------+------+---------+-------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+-------+---------------+------+---------+-------+---------+----------+-------------+
| 1 | UPDATE | users_ram | NULL | range | sub | sub | 5 | const | 1166666 | 100.00 | Using where |
+----+-------------+-----------+------------+-------+---------------+------+---------+-------+---------+----------+-------------+
1 row in set (0.00 sec)
mysql> update users_ram set balance = balance + speed where sub = 1;
Query OK, 1166970 rows affected (0.37 sec)
Rows matched: 1166970 Changed: 1166970 Warnings: 0
If I remove the index and add it again, but now using btree, it gets even more weird
mysql> explain update users_ram set balance = balance + speed where sub = 1;
+----+-------------+-----------+------------+-------+---------------+------+---------+-------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+-------+---------------+------+---------+-------+---------+----------+-------------+
| 1 | UPDATE | users_ram | NULL | range | sub | sub | 5 | const | 1057987 | 100.00 | Using where |
+----+-------------+-----------+------------+-------+---------------+------+---------+-------+---------+----------+-------------+
1 row in set (0.00 sec)
mysql> update users_ram set balance = balance + speed where sub = 1;
Query OK, 1166970 rows affected (0.62 sec)
Rows matched: 1166970 Changed: 1166970 Warnings: 0
How could adding an index could have no effect or even slow down the query?
Let's take into account that I'm not modifying the column which is indexed, so mysql doesn't have to do an extra write operation, so really I can't get what's really happening here.
"table located in RAM" -- I suspect that is technically incorrect. The possibilities (in MySQL):
The table lives on disk, but it is usually fully cached in the in-RAM "buffer_pool".
The table is ENGINE=MEMORY. But that is used only for temp stuff; it is completely lost if the server goes down.
update users_ram set balance = balance + speed where sub = 1;
The table users_ram needs some index starting with sub. With such, it can go directly to the row(s). But...
It seems that there are 1166970 such rows. That seems like half the table?? At which point, the index is pretty useless. But...
Updating 1M rows is terribly slow, regardless of indexing.
Plan A: Avoid the UPDATE. Perhaps this can be done by storing speed in some other table and doing the + whenever you read the data. (It is generally bad schema design to need huge updates like that.)
Plan B: Update in chunks: http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks
How the heck did you get index-type to be HASH? Perhaps `ENGINE=MEMORY? What version of MySQL?
What is speed? Another column? A constant?
Please provide SHOW CREATE TABLE users_ram -- There are some other things we need to see, such as the PRIMARY KEY and ENGINE.
(I need some of the above info before tackling "How could adding an index could have no effect or even slow down the query?")

MARIADB - Can SQL convert NULL to an empty string if no default value is provided?

I have a table that has nullable columns:
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| name | varchar(255) | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
I insert a row with name set to NULL;
INSERT INTO some_table (id, name) VALUES (1, NULL);
Query OK, 1 row affected (0.02 sec)
SELECT * FROM some_table;
+------+------+
| id | name |
+------+------+
| 1 | NULL |
+------+------+
1 row in set (0.01 sec)
If I alter the table's name column to be not-nullable it apparently converts NULL to an empty string:
ALTER TABLE some_table CHANGE COLUMN name name VARCHAR(255) NOT NULL;
Query OK, 1 row affected, 1 warning (0.02 sec)
Records: 1 Duplicates: 0 Warnings: 1
SELECT * FROM some_table;
+------+------+
| id | name |
+------+------+
| 1 | |
+------+------+
1 row in set (0.02 sec)
At this point I would expect an exception to be raised telling me that I have NULL in my dataset and I can not set the column name to NOT NULL.
Is this a configurable option in SQL/MariaDB?
Why is NULL being converted to an empty string?
There is a warning being invoked when altering the table:
SHOW WARNINGS;
+---------+------+-------------------------------------------+
| Level | Code | Message |
+---------+------+-------------------------------------------+
| Warning | 1265 | Data truncated for column 'name' at row 1 |
+---------+------+-------------------------------------------+
1 row in set (0.01 sec)
Version:
SELECT version();
+----------------+
| version() |
+----------------+
| 5.5.62-MariaDB |
+----------------+
1 row in set (0.02 sec)
Apparently, from the documentation for ALTER TABLE, enabling strict mode would prevent your alter statement from succeeding:
This conversion may result in alteration of data. For example, if you shorten a string column, values may be truncated. To prevent the operation from succeeding if conversions to the new data type would result in loss of data, enable strict SQL mode before using ALTER TABLE.
One way to enable strict mode from within MySQL:
SET GLOBAL sql_mode='STRICT_TRANS_TABLES';
See here for other options.
Using 10.3.15-MariaDB-1 on Debian Buster, I cannot reproduce the problem:
MariaDB [foo]> CREATE TABLE some_table(id int(11), name varchar(255));
Query OK, 0 rows affected (0.009 sec)
MariaDB [foo]> INSERT INTO some_table (id, name) VALUES (1, NULL);
Query OK, 1 row affected (0.003 sec)
MariaDB [foo]> SELECT * FROM some_table;
+------+------+
| id | name |
+------+------+
| 1 | NULL |
+------+------+
1 row in set (0.000 sec)
MariaDB [foo]> ALTER TABLE some_table CHANGE COLUMN name name VARCHAR(255) NOT NULL;
ERROR 1265 (01000): Data truncated for column 'name' at row 1
MariaDB [foo]> SELECT * FROM some_table;
+------+------+
| id | name |
+------+------+
| 1 | NULL |
+------+------+
1 row in set (0.000 sec)
MariaDB [foo]> SELECT version();
+-------------------+
| version() |
+-------------------+
| 10.3.15-MariaDB-1 |
+-------------------+
1 row in set (0.000 sec)
If possible, I suggest you update your MariaDB version. It seems very old to me.

MySQL INSERT Statement Inserting Too Many Rows

I'm trying to insert a row in a MySQL if a row already exists in the same table with nick in it. Here is the query I'm attempting:
INSERT IGNORE INTO gold_log (nick, amount, stream_online, modification_type, dt)
SELECT DISTINCT nick, 0, 0, 253, DATE_FORMAT(NOW(), '%Y-01-01 00:00:00')
FROM gold_log WHERE nick='PrestonConnors';
I get result:
Query OK, 2243 rows affected, 2 warnings (0.24 sec)
Records: 2243 Duplicates: 0 Warnings: 2
When I run the SELECT statement independently it only returns one result (which is what I expect):
mysql> SELECT DISTINCT nick, 0, 0, 253, DATE_FORMAT(NOW(), '%Y-01-01 00:00:00') FROM gold_log WHERE nick='PrestonConnors';
+----------------+---+---+-----+-----------------------------------------+
| nick | 0 | 0 | 253 | DATE_FORMAT(NOW(), '%Y-01-01 00:00:00') |
+----------------+---+---+-----+-----------------------------------------+
| PrestonConnors | 0 | 0 | 253 | 2015-01-01 00:00:00 |
+----------------+---+---+-----+-----------------------------------------+
1 row in set (0.01 sec)
Can someone help form my INSERT IGNORE INTO statement into one that will only INSERT one row into the table and also explain what was wrong with my query?
Here is the layout of the table:
mysql> describe gold_log;
+-------------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+---------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| nick | char(25) | NO | PRI | NULL | |
| amount | decimal(10,4) | YES | MUL | NULL | |
| stream_online | tinyint(1) | NO | MUL | NULL | |
| modification_type | tinyint(3) unsigned | NO | MUL | NULL | |
| dt | datetime | NO | PRI | NULL | |
+-------------------+---------------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
And here are the warnings:
mysql> SHOW warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1592 | Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT IGNORE... SELECT is unsafe because the order in which rows are retrieved by the SELECT determines which (if any) rows are ignored. This order cannot be predicted and may differ on master and the slave. |
| Note | 1592 | Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statements writing to a table with an auto-increment column after selecting from another table are unsafe because the order in which rows are retrieved determines what (if any) rows will be written. This order cannot be predicted and may differ on master and the slave. |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
Simply, It is a bug in MySQL (I found it in mysql5.5.16); but when I update to (5.6.24):
mysql> status
--------------
D:\xampp\mysql\bin\mysql.exe Ver 14.14 Distrib 5.6.24, for Win32 (x86)
It works properly.
See:
http://bugs.mysql.com/bug.php?id=58637
http://bugs.mysql.com/bug.php?id=72921

What is the true range of possible values of a field type 'DATE'?

DATE field type has limitations '1000-01-01' -'9999-12-31'.
BUT Why inserted '987-10-10' not '1000-01-01' ?
mysql> INSERT INTO DATE12(datas) VALUES('987-10-10');
Query OK, 1 row affected (0.03 sec)
mysql> SELECT * FROM DATE12;
+------+------------+
| id | datas |
+------+------------+
| NULL | 1987-11-04 |
| NULL | 0987-10-10 |
+------+------------+
2 rows in set (0.00 sec)
incorrect data format
mysql> INSERT INTO DATE12(datas) VALUES('10001-13-12');
Query OK, 1 row affected, 1 warning (0.03 sec)
mysql> SELECT * FROM DATE12;
+------+------------+
| id | datas |
+------+------------+
| NULL | 1987-11-04 |
| NULL | 0987-10-10 |
| NULL | 0000-00-00 |
But for time type all good
mysql> INSERT INTO time1(t) VALUE('-1112:45:12');
Query OK, 1 row affected, 1 warning (0.02 sec)
mysql> SELECT * FROM time1;
+------+------------+
| id | t |
+------+------------+
| NULL | NULL |
| NULL | -12:45:12 |
| NULL | -838:59:59 |
+------+------------+
3 rows in set (0.00 sec)
mysql> INSERT INTO time1(t) VALUE('11112:45:12');
Query OK, 1 row affected, 1 warning (0.02 sec)
mysql> SELECT * FROM time1;
+------+------------+
| id | t |
+------+------------+
| NULL | NULL |
| NULL | -12:45:12 |
| NULL | -838:59:59 |
| NULL | 838:59:59 |
+------+------------+
4 rows in set (0.00 sec)
mysql>
...Why insered '987-10-10' not '1000-01-01' ?
As per documentation on The DATE, DATETIME, and TIMESTAMP Types
For the DATE and DATETIME range descriptions, “supported” means that although earlier values might work, there is no guarantee.
Meaning, the dates prior to '1000-01-01' may also be accepted, but not guaranteed.
And hence, in your case the date entry '987-10-10' is accepted with no errors.
And you claim that '1000-01-01' is not inserted. But you actually tried '10001-13-12'.
And even if you try with '1000-13-12', it will fail, because, value '13' for 'month' part is meaning less.
As per documentation on Date and Time Literals
As a string with no delimiters in either 'YYYYMMDD' or 'YYMMDD' format, provided that the string makes sense as a date. For example, '20070523' and '070523' are interpreted as '2007-05-23', but '071332' is illegal (it has nonsensical month and day parts) and becomes '0000-00-00'.
Though not seen in the documentation, the above statement is also valid for format like 'YYYY-MM-DD' and others. And the same is shown in the following example:
mysql> insert into vdt(dt) values( '1001-13-32' );
ERROR 1292 (22007): Incorrect date value: '1001-13-32' for column 'dt' at row 1
mysql> select date_format( '2013-13-32', '%Y-%m-%d' ); show warnings;
+-----------------------------------------+
| date_format( '2013-13-32', '%Y-%m-%d' ) |
+-----------------------------------------+
| NULL |
+-----------------------------------------+
1 row in set, 1 warning (0.00 sec)
+---------+------+----------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------+
| Warning | 1292 | Incorrect datetime value: '2013-13-32' |
+---------+------+----------------------------------------+
1 row in set (0.00 sec)
mysql> select str_to_date( '20131232', '%Y%m%d' ); show warnings;
+-------------------------------------+
| str_to_date( '20131232', '%Y%m%d' ) |
+-------------------------------------+
| NULL |
+-------------------------------------+
1 row in set, 1 warning (0.00 sec)
+---------+------+---------------------------------------------------------------+
| Level | Code | Message |
+---------+------+---------------------------------------------------------------+
| Warning | 1411 | Incorrect datetime value: '20131232' for function str_to_date |
+---------+------+---------------------------------------------------------------+