ON DUPLICATE KEY UPDATE - decrement value in MySQL - mysql

The following seems odds to me:
INSERT INTO sometable (UNIQUEVALUE,NUMERICVALUE) VALUES ('valuethatexists','100') ON DUPLICATE KEY UPDATE NUMERICVALUE = NUMERICVALUE+VALUES(NUMERICVALUE);
Assume your NUMERICVALUE is at 0.
The above would change it to 100 - which does work.
If, however, you then input -100, it does not work properly.
INSERT INTO sometable (UNIQUEVALUE,NUMERICVALUE) VALUES ('valuethatexists','-100') ON DUPLICATE KEY UPDATE NUMERICVALUE = NUMERICVALUE+VALUES(NUMERICVALUE);
The above statement should return it to 0. It does not, in my case. It remains at 100.
Am I missing something?
Edit: This goes wrong somewhere else. I am doing this with PHP. The actual code exhibiting this bug looks like this:
Edit 2: This had nothing to do with PHP either. The problem was the NUMERIC value was UNSIGNED in my production environment, meaning VALUES(NUMERICVALUE) was brought from -100 to 0 before it was used.

On my MySQL server (5.7.12), it does work as expected:
mysql> CREATE TABLE sometable (
UNIQUEVALUE VARCHAR(16) NOT NULL PRIMARY KEY,
NUMERICVALUE INT NOT NULL);
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO sometable (UNIQUEVALUE,NUMERICVALUE)
VALUES ('valuethatexists','100')
ON DUPLICATE KEY UPDATE NUMERICVALUE = NUMERICVALUE+VALUES(NUMERICVALUE);
Query OK, 1 row affected (0.01 sec)
mysql> SELECT * FROM sometable;
+-----------------+--------------+
| UNIQUEVALUE | NUMERICVALUE |
+-----------------+--------------+
| valuethatexists | 100 |
+-----------------+--------------+
1 row in set (0.00 sec)
mysql> INSERT INTO sometable (UNIQUEVALUE,NUMERICVALUE)
VALUES ('valuethatexists','-100')
ON DUPLICATE KEY UPDATE NUMERICVALUE = NUMERICVALUE+VALUES(NUMERICVALUE);
Query OK, 2 rows affected (0.00 sec)
mysql> SELECT * FROM sometable;
+-----------------+--------------+
| UNIQUEVALUE | NUMERICVALUE |
+-----------------+--------------+
| valuethatexists | 0 |
+-----------------+--------------+
1 row in set (0.00 sec)
Which version of MySQL are you using? Can you execute the exact statements above and see if you have different results?

While Benjamin's answer is correct, the root of the issue turned out to be the fact that the NUMERICVALUE column was UNSIGNED, so whenever I input -100, it was turned into 0 before it was evaluated as VALUES(NUMERICVALUE). If this is to be considered a bug or not I don't know.
Obviously the result of the final evaluation should not be negative, but I don't know how clever it is to silently turn it into 0. I had logic in place making sure the value in question would never be below 0 anyway by never passing a negative value larger than what was already in the row.

Related

Can I make mysql table columns case insensitive?

I am new to mysql (and sql in general) and am trying to see if I can make data inserted into a column in a table case insensitive.
I am storing data like state names, city names, etc. So I want to have a unique constraint on these types of data and on top of that make them case insensitive so that I can rely on the uniqueness constraint.
Does mysql support a case-insensitive option on either the column during table creation or alternatively when setting the uniqueness constraint on the column? What is the usual way to deal with such issues? I would appreciate any alternate ideas/suggestions to deal with this.
EDIT: As suggested, does COLLATE I think only applies to queries on the inserted data. But to really take advantage of the uniqueness contraint, I want to have a case insensitivity restriction on INSERT. For e.g. I want mysql to not allow insertions of California and california and cALifornia as they should be the same. But if I understand the uniqueness constraint prooperly, having it on the StateName will still allow the above four inserts.
By default, MySQL is case-insensitive.
CREATE TABLE test
(
name VARCHAR(20),
UNIQUE(name)
);
mysql> INSERT INTO test VALUES('California');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO test VALUES('california');
ERROR 1062 (23000): Duplicate entry 'california' for key 'name'
mysql> INSERT INTO test VALUES('cAlifornia');
ERROR 1062 (23000): Duplicate entry 'cAlifornia' for key 'name'
mysql> INSERT INTO test VALUES('cALifornia');
ERROR 1062 (23000): Duplicate entry 'cALifornia' for key 'name'
mysql> SELECT * FROM test;
+------------+
| name |
+------------+
| California |
+------------+
1 row in set (0.00 sec)
Use BINARY when you need case-sensitivity
To make case-sensitive in MySQL, BINARY keyword is used as follows
mysql> CREATE TABLE test
-> (
-> name varchar(20) BINARY,
-> UNIQUE(name)
-> );
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> INSERT INTO test VALUES('California');
Query OK, 1 row affected (0.00 sec)
mysql>
mysql> INSERT INTO test VALUES('california');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO test VALUES('cAlifornia');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO test VALUES('cALifornia');
Query OK, 1 row affected (0.00 sec)
mysql>
mysql> SELECT * FROM test;
+------------+
| name |
+------------+
| California |
| cALifornia |
| cAlifornia |
| california |
+------------+
4 rows in set (0.00 sec)
You can use COLLATE operator: http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html

How do I detect if the ON UPDATE event fired with query in MYSQL?

Say I have a query with an ON DUPLICATE stanza:
INSERT INTO table (a) VALUES (0)
ON DUPLICATE KEY UPDATE a=1
How do I find out, after I've run this query, whether the query performed an insert, or an update?
The ROW_COUNT function can discriminate, returning 1 or 2 if the INSERT was "pure" or collided.
Per the docs:
For INSERT ... ON DUPLICATE KEY UPDATE statements, the affected-rows value is 1 if the row is inserted as a new row and 2 if an existing row is updated.
Example:
mysql> create table t (a int not null unique) engine=innodb;
Query OK, 0 rows affected (0.05 sec)
mysql> insert into t (a) values (0) on duplicate key update a=1;
Query OK, 1 row affected (0.00 sec)
mysql> select row_count();
+-------------+
| row_count() |
+-------------+
| 1 |
+-------------+
1 row in set (0.00 sec)
mysql> insert into t (a) values (0) on duplicate key update a=1;
Query OK, 2 rows affected (0.00 sec)
mysql> select row_count();
+-------------+
| row_count() |
+-------------+
| 2 |
+-------------+
1 row in set (0.00 sec)
You can use an extra column for the if, and name it as flag (int or tinyint) and set its default to 0, and change query like:
INSERT INTO table (a, flag) VALUES (0, 1) ON DUPLICATE KEY UPDATE a=1;
Now if insert is successful, the flag is set to 1 otherwise it's 0.
On a professional level, this flag example is very useful in many ways.

MySQL gender table field

If I want to create a gender field in my table, how do I make sure that my database doesn't accept any value apart from "M" or "F" ?
$sqlCommand = "CREATE TABLE members (
id int(11) NOT NULL auto_increment,
...
...
...
...
gender
)";
Thank you
No triggers, no enums or other deamonic activities.
You can use a FOREIGN KEY to a reference table with just 2 rows:
CREATE TABLE Gender_Ref
( gender CHAR(1) NOT NULL,
PRIMARY KEY (gender)
) ENGINE = InnoDB ;
INSERT INTO Gender_Ref (gender)
VALUES
('F'), ('M') ;
CREATE TABLE members
( id int(11) NOT NULL auto_increment,
...
...
gender CHAR(1) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY gender
REFERENCES Gender_Ref (gender)
) ENGINE = InnoDB ;
It's also good advice to "lock" the reference table so the applications code has only read access. (That's usually good for most reference tables, and if you have an Admin application, you can of course give it write access as well to the reference tables).
Like pointed out in the comments, you can use ENUM like so:
gender ENUM('F','M') NOT NULL
However, you have to be careful as this will still accept the empty string too (although you'll get a warning for that):
mysql> create table t (g enum('M','F') not null);
Query OK, 0 rows affected (0.12 sec)
mysql> insert into t values ('M');
Query OK, 1 row affected (0.00 sec)
mysql> insert into t values ('');
Query OK, 1 row affected, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+----------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------+
| Warning | 1265 | Data truncated for column 'g' at row 1 |
+---------+------+----------------------------------------+
1 row in set (0.00 sec)
mysql> select * from t;
+---+
| g |
+---+
| M |
| |
+---+
2 rows in set (0.00 sec)
To ensure this does not happen, you could consider setting the sql_mode (http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html) to a more restrictive value:
mysql> set sql_mode = strict_all_tables;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t values ('');
ERROR 1265 (01000): Data truncated for column 'g' at row 1
However, you should investigate if this is a suitable option for you. Many existing applications (wordpress etc) don't like messing with the sql_mode so if your code is a plugin to those systems you want to avoid setting it.
You can choose to set the sql_mode server wide or session wide; The first option would be more robust, but requires configuring MySQL in a non default way, and is likely to affect other applications. Setting at the session level immediately after you open the connection should work just fine, but will clutter your application code. Pick your poison.
ypercube's suggestion to use a foreign key is also good, and is more portable to other RDBMSes than ENUM. However, you'll have to ensure your tables are both managed by the InnoDB engine. This is becoming more and more the standard so it's not a bad choice.
(if you're really paranoid, you should really ensure that the application only has read access to the gender reference table)
You could use enum type enum('M','F').
You can use a trigger to check if it is coreect or you can use an enum type enum('M','F')

MySQL bulk INSERT or UPDATE

Is there any way of performing in bulk a query like INSERT OR UPDATE on the MySQL server?
INSERT IGNORE ...
won't work, because if the field already exists, it will simply ignore it and not insert anything.
REPLACE ...
won't work, because if the field already exists, it will first DELETE it and then INSERT it again, rather than updating it.
INSERT ... ON DUPLICATE KEY UPDATE
will work, but it can't be used in bulk.
So I'd like to know if there's any command like INSERT ... ON DUPLICATE KEY UPDATE that can be issued in bulk (more than one row at the same time).
You can insert/update multiple rows using INSERT ... ON DUPLICATE KEY UPDATE. The documentation has the following example:
INSERT INTO table (a,b,c) VALUES (1,2,3),(4,5,6)
ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b);
Or am I misunderstanding your question?
One possible way to do this is to create a temporary table, insert the data into that, and then do 1 query with a join to insert the records that don't exist followed by and update to the fields that do exist. The basics would be something like this.
CREATE TABLE MyTable_Temp LIKE MyTable
LOAD DATA INFILE..... INTO MyTable_Temp
UPDATE MyTable INNER JOIN
MyTable_Temp
ON MyTable.ID=MyTable_Temp.ID
SET MyTable.Col1=MyTable_Temp.Col1, MyTable.Col2=MyTable_Temp.Col2.....
INSERT INTO MyTable(ID,Col1,Col2,...)
SELECT ID,Col1,Col2,...
FROM MyTable_Temp
LEFT JOIN MyTable
ON MyTable_Temp.ID = MyTable.ID
WHERE myTable.ID IS NULL
DROP TABLE MyTable_Temp
The syntax may not be exact, but this should give you the basics. Also, I know it's not pretty, but it gets the job done.
Update
I swapped the order of the insert and update, because doing insert first causes all the inserted rows to be updated when the update is called. If you do update first, only the existing records are updated. This should mean a little less work for the server, although the results should be the same.
Although this question has been answered correctly already (that MySQL does support this via ON DUPLICATE UPDATE with the expected multiple value set syntax), I'd like to expand on this by providing a demonstration that anyone with MySQL can run:
CREATE SCHEMA IF NOT EXISTS `test`;
DROP TABLE IF EXISTS test.new_table;
CREATE TABLE test.new_table (`Key` int(11) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`Key`)) ENGINE=InnoDB AUTO_INCREMENT=106 DEFAULT CHARSET=utf8;
SELECT * FROM test.new_table;
INSERT INTO test.new_table VALUES (1),(2),(3),(4),(5) ON DUPLICATE KEY UPDATE `Key`=`Key`+100;
SELECT * FROM test.new_table;
INSERT INTO test.new_table VALUES (1),(2),(3),(4),(5) ON DUPLICATE KEY UPDATE `Key`=`Key`+100;
SELECT * FROM test.new_table;
The output is as follows:
Empty set (0.00 sec)
Query OK, 5 rows affected (0.00 sec)
Records: 5 Duplicates: 0 Warnings: 0
+-----+
| Key |
+-----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
+-----+
5 rows in set (0.00 sec)
Query OK, 10 rows affected (0.00 sec)
Records: 5 Duplicates: 5 Warnings: 0
+-----+
| Key |
+-----+
| 101 |
| 102 |
| 103 |
| 104 |
| 105 |
+-----+
5 rows in set (0.00 sec)
Try adding an insert trigger that does a pre-flight check and cancels the insert on duplicate key (after updating the existing row).
Not sure it'll scale well for bulk inserts, let alone work for load data infile, but it's the best I can think of. :-)
If you were using Oracle or Microsoft SQL, you could use the MERGE. However, MySQL does not have a direct correlation to that statement. There is the single-row solution that you mentioned but, as you pointed out, it doesn't do bulk very well. Here is a blog post I found on the difference between Oracle and MySQL and how to do what Oracle does with MERGE in MySQL:
http://blog.mclaughlinsoftware.com/2009/05/25/mysql-merge-gone-awry/
It isn't a pretty solution and it probably isn't as full a solution as you would like, but I believe that is the best there is for a solution.

MySQL auto increment

I have table with an auto-increment field, but I need to transfer the table to another table on another database. Will the value of field 1 be 1, that of field 2 be 2, etc?
Also in case the database get corrupted and I need to restore the data, will the auto-increment effect in some way? will the value change? (eg if the first row, id (auto-inc) = 1, name = john, country = UK .... will the id field remain 1?) I am asking because if other table refer to this value, all data will get out of sync if this field change.
It sounds like you are trying to separately insert data into two separate databases in the same order, and using the auto-increment field to link the two rows. It seems you are basically asking, is it OK to rely on the auto-increment being the same in both databases if the data is inserted in the same order.
If so, the answer is no - you cannot rely on this behaviour. It is legitimate for the auto-increment to skip a value, for example see here.
But maybe you are asking, can an auto-increment value suddenly change to another value after it is written and committed? No - they will not change in the future (unless of course you change them explicitly).
Does that answer your question? If not, perhaps you can explain your question again.
Transferring the data wouldn't be a problem, if you completely specify the auto_increment values. MySQL allows you to insert anything you want into an auto_increment field, but only does the actual auto_increment if the value you're inserting is 0 or NULL. At least on my 5.0 copy of MySQL, it'll automatically adjust the auto_increment value to take into account what you've inserted:
mysql> create table test (x int auto_increment primary key);
Query OK, 0 rows affected (0.01 sec)
mysql> insert into test (x) values (10);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test (x) values (null);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test (x) values (0);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test (x) values (5);
Query OK, 1 row affected (0.00 sec)
mysql> select * from test;
+----+
| x |
+----+
| 5 | <--inserted '5' (#4)
| 10 | <--inserted '10' (#1)
| 11 | <--inserted 'null' (#2)
| 12 | <--inserted '0' (#3)
+----+
3 rows in set (0.00 sec)
You can also adjust the table's next auto_increment value as follows:
mysql> alter table test auto_increment=500;
Query OK, 4 rows affected (0.04 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> insert into test (x) values (null);
Query OK, 1 row affected (0.00 sec)
mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
| 500 |
+------------------+
1 row in set (0.01 sec)
SELECT INTO should keep the same ids on target table
http://dev.mysql.com/doc/refman/5.1/en/ansi-diff-select-into-table.html
Using MySQL backup will do this, if you create your own insert statements make sure that you include your id field and that will insert the value (its not like MSSQL where you have to set identity_insert), a thing to watch for is that if you generate a DDL it sometimes generates "incrorectly" for your identity column (i.e. it states that starting point is at your last identity value? you may not want this behaviour).