Description:
During an ON DUPLICATE KEY UPDATE if a column is evaluated and a new value assigned to it, subsequent evaluations on that column use the newly set value rather than the original value.
How to repeat:
Create a table:
CREATE TABLE `sample` (
`a` int NOT NULL,
`b` text,
`c` text,
PRIMARY KEY (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Insert a row:
INSERT INTO sample VALUES(1,'b1','c1');
Data at this point:
SELECT * FROM sample;
+---+------+------+
| a | b | c |
+---+------+------+
| 1 | b1 | c1 |
+---+------+------+
1 row in set (0.00 sec)
Perform an INSERT ... ON DUPLICATE KEY with logic evaluating b and c to set values for both b and subsequently c:
INSERT INTO sample (a, b, c)
-> VALUES (1, "DONT_CARE_B_VAL", "DONT_CARE_C_VAL")
-> ON DUPLICATE KEY UPDATE
-> b = IF(b = "b1" AND c = "c1", "b2", "BAD_B_VAL"),
-> c = IF(b = "b1" AND c = "c1", "c2", "BAD_C_VAL");
The expected results would be 1, b2, c2 but instead:
SELECT * FROM sample;
+---+------+-----------+
| a | b | c |
+---+------+-----------+
| 1 | b2 | BAD_C_VAL |
+---+------+-----------+
This behavior is counter to how SQL operates in other ways. For example:
SELECT
IF(a=1,2,0) AS a
FROM
sample
WHERE
a=1
AND
a<2;
Will return 2 as expected, it does not set `a` to 2 after the first evaluation.
Note that the UPDATE is written to the binary log as a single statement, and with the correct values in the field comparisons:
SET TIMESTAMP=1741040611/*!*/;
BEGIN
/*!*/;
# at 2875
#250303 22:23:31 server id 100 end_log_pos 2936 CRC32 0x1e6bc0cd Table_map: `test`.`sample` mapped to number 99
# has_generated_invisible_primary_key=0
# at 2936
#250303 22:23:31 server id 100 end_log_pos 3005 CRC32 0x3c1fe2e4 Update_rows: table id 99 flags: STMT_END_F
### UPDATE `test`.`sample`
### WHERE
### @1=1
### @2='b1'
### @3='c1'
### SET
### @1=1
### @2='b2'
### @3='BAD_C_VAL'
# at 3005
#250303 22:23:31 server id 100 end_log_pos 3036 CRC32 0x12f0c321 Xid = 64
COMMIT/*!*/;
Suggested fix:
Do not change the transient row object prior to evaluating the entire transaction, or provide a means (e.g. OLD.field) of evaluating constants during the operation.
In any case, this behavior does not appear to be documented anywhere that I could find. Regardless of the decision to change the behavior described or not, please at least provide full documentation on the behavior.
Cheers!
Dov Endress
Description: During an ON DUPLICATE KEY UPDATE if a column is evaluated and a new value assigned to it, subsequent evaluations on that column use the newly set value rather than the original value. How to repeat: Create a table: CREATE TABLE `sample` ( `a` int NOT NULL, `b` text, `c` text, PRIMARY KEY (`a`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; Insert a row: INSERT INTO sample VALUES(1,'b1','c1'); Data at this point: SELECT * FROM sample; +---+------+------+ | a | b | c | +---+------+------+ | 1 | b1 | c1 | +---+------+------+ 1 row in set (0.00 sec) Perform an INSERT ... ON DUPLICATE KEY with logic evaluating b and c to set values for both b and subsequently c: INSERT INTO sample (a, b, c) -> VALUES (1, "DONT_CARE_B_VAL", "DONT_CARE_C_VAL") -> ON DUPLICATE KEY UPDATE -> b = IF(b = "b1" AND c = "c1", "b2", "BAD_B_VAL"), -> c = IF(b = "b1" AND c = "c1", "c2", "BAD_C_VAL"); The expected results would be 1, b2, c2 but instead: SELECT * FROM sample; +---+------+-----------+ | a | b | c | +---+------+-----------+ | 1 | b2 | BAD_C_VAL | +---+------+-----------+ This behavior is counter to how SQL operates in other ways. For example: SELECT IF(a=1,2,0) AS a FROM sample WHERE a=1 AND a<2; Will return 2 as expected, it does not set `a` to 2 after the first evaluation. Note that the UPDATE is written to the binary log as a single statement, and with the correct values in the field comparisons: SET TIMESTAMP=1741040611/*!*/; BEGIN /*!*/; # at 2875 #250303 22:23:31 server id 100 end_log_pos 2936 CRC32 0x1e6bc0cd Table_map: `test`.`sample` mapped to number 99 # has_generated_invisible_primary_key=0 # at 2936 #250303 22:23:31 server id 100 end_log_pos 3005 CRC32 0x3c1fe2e4 Update_rows: table id 99 flags: STMT_END_F ### UPDATE `test`.`sample` ### WHERE ### @1=1 ### @2='b1' ### @3='c1' ### SET ### @1=1 ### @2='b2' ### @3='BAD_C_VAL' # at 3005 #250303 22:23:31 server id 100 end_log_pos 3036 CRC32 0x12f0c321 Xid = 64 COMMIT/*!*/; Suggested fix: Do not change the transient row object prior to evaluating the entire transaction, or provide a means (e.g. OLD.field) of evaluating constants during the operation. In any case, this behavior does not appear to be documented anywhere that I could find. Regardless of the decision to change the behavior described or not, please at least provide full documentation on the behavior. Cheers! Dov Endress