conditional on duplicate key update - sql

I'm trying to insert a new row, but if the key already exists, I want to update the row ONLY if a certain other value is in the table is different. Is this possible in a mysql query/statement?
My table consist of the following columns: hat, mittens, name, last_update
hat+mittens make up the unique index (say the values for "hat" and "mittens" are colors)
Let's assume this is already in the table:
1. hat=blue mittens=green name=george last_update=tuesday
2. hat=red mittens=green name=bill last_update=monday
On a new key, I want to insert as usual. On duplicate key, I want to do an update ONLY IF the name changes, otherwise ignore. The reason for this is that I want to preserve the last_update value (timestamp).
hat=yellow mittens=purple name=jimmy -- insert new row
hat=blue mittens=green name=george -- ignore
hat=blue mittens=green name=betty -- update row
Is this possible without using separate statements to first look up the existing row, compare values and then issue an update if necessary? If so, what would be the syntax?
Thanks for your responses. I tried all of them. Indeed, using just a simple UPDATE statement like
update tbl set name='george' where hat='blue' and mittens='green'
results in no rows being updated. But, using either
INSERT INTO tbl (hat,mittens,name) VALUES ('blue','green','george') ON DUPLICATE KEY UPDATE name='george';
or
INSERT INTO tbl (hat, mittens, name) VALUES ('blue','green','george') ON DUPLICATE KEY UPDATE name=CASE WHEN name <> VALUES(name) THEN VALUES(name) ELSE name END;
somehow results in the row being updated (and the timestamp changed).
FWIW, this is the table I'm using:
CREATE TABLE `tbl` (
`hat` varchar(11) default NULL,
`mittens` varchar(11) default NULL,
`name` varchar(11) default NULL,
`stamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
UNIQUE KEY `clothes` (`hat`,`mittens`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
MySQL is version 4.1.22 (perhaps this matters?)
Again, my appreciation for all of the replies.

You can use normal sql constructs in the ON DUPLICATE KEY syntax. So in order to do conditional updates during an insert you can do the following:
INSERT INTO tbl (hat, mittens, name)
VALUES ('yellow','purple','jimmy')
ON DUPLICATE KEY UPDATE name = CASE WHEN name <> VALUES(name)
THEN VALUES(name) ELSE name END;
This will change the value to what you provided to the insert statement when it's different from what's in the row and will set the value to be what it already is if it hasn't changed and will result in MySQL not doing anything to the row preserving the last_update timestamp as Quassnoi pointed out.
If you wanted to make 100% sure that you weren't relying on the behavior of MySQL where it doesn't update a row if you set a value to itself you can do the following to force the timestamp:
INSERT INTO tbl (hat, mittens, name)
VALUES ('yellow','purple','jimmy')
ON DUPLICATE KEY UPDATE name = CASE WHEN name <> VALUES(name)
THEN VALUES(name) ELSE name END
, last_update = CASE WHEN name <> VALUES(name)
THEN now() ELSE last_update END;
This will only update the last_update to now() when the name has changed else it will tell MySQL to retain the value of last_update.
Also, in the ON DUPLICATE KEY section of the statement you can refer to the columns in the table by their name and you can get the values that you provided to the insert statement values section using the VALUES(column_name) function.
The following is a log that shows that the last statement provided works even on 4.1 where the others don't work due to a bug that was fixed in version 5.0.
C:\mysql\bin>mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1 to server version: 4.1.22-community
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> show databases;
+----------+
| Database |
+----------+
| mysql |
| test |
+----------+
2 rows in set (0.00 sec)
mysql> use test;
Database changed
mysql> show tables;
Empty set (0.00 sec)
mysql> CREATE TABLE `tbl` (
-> `hat` varchar(11) default NULL,
-> `mittens` varchar(11) default NULL,
-> `name` varchar(11) default NULL,
-> `stamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
-> UNIQUE KEY `clothes` (`hat`,`mittens`)
-> ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO tbl (hat,mittens,name) VALUES ('blue','green','george');
Query OK, 1 row affected (0.00 sec)
mysql> select * from tbl;
+------+---------+--------+---------------------+
| hat | mittens | name | stamp |
+------+---------+--------+---------------------+
| blue | green | george | 2009-06-27 12:15:16 |
+------+---------+--------+---------------------+
1 row in set (0.00 sec)
mysql> INSERT INTO tbl (hat,mittens,name) VALUES ('blue','green','george') ON DUPLICATE KEY UPDATE name='george';
Query OK, 2 rows affected (0.00 sec)
mysql> select * from tbl;
+------+---------+--------+---------------------+
| hat | mittens | name | stamp |
+------+---------+--------+---------------------+
| blue | green | george | 2009-06-27 12:15:30 |
+------+---------+--------+---------------------+
1 row in set (0.00 sec)
mysql> INSERT INTO tbl (hat, mittens, name) VALUES ('blue','green','george') ON DUPLICATE KEY UPDATE name=CASE WHEN name <> VALUES(name) THEN VALUES(name) ELSE name END;
Query OK, 2 rows affected (0.00 sec)
mysql> select * from tbl;
+------+---------+--------+---------------------+
| hat | mittens | name | stamp |
+------+---------+--------+---------------------+
| blue | green | george | 2009-06-27 12:15:42 |
+------+---------+--------+---------------------+
1 row in set (0.00 sec)
mysql> INSERT INTO tbl (hat,mittens,name) VALUES ('blue','green','george') ON DUPLICATE KEY UPDATE name = CASE WHEN name <> VALUES(name) THEN VALUES(name) ELSE name END, stamp = CASE WHEN name <> VALUES(name) THEN now() ELSE stamp END;
Query OK, 2 rows affected (0.00 sec)
mysql> select * from tbl;
+------+---------+--------+---------------------+
| hat | mittens | name | stamp |
+------+---------+--------+---------------------+
| blue | green | george | 2009-06-27 12:15:42 |
+------+---------+--------+---------------------+
1 row in set (0.00 sec)
mysql>
Let me know if you have any questions.
HTH,
-Dipin

You need INSERT ... ON DUPLICATE KEY UPDATE syntax.
Your query would look like this:
INSERT INTO tbl (hat,mittens,name) VALUES ('blue','green','george')
ON DUPLICATE KEY UPDATE name='george';
If you had a record with blue/green/george for hat/mittens/name already, no UPDATE would actually be performed, and your timestamp would not be updated. If however you had a record with blue/green/betty, then 'betty' would be overwritten with 'george', and your timestamp would be updated.

If you are performing a multiple INSERT (either by SELECT or providing multiple rows to VALUES), you can do the following:
INSERT INTO tbl (hat,mittens,name) VALUES
('yellow','purple','jimmy'),
('blue','green','george'),
('blue','green','betty')
ON DUPLICATE KEY UPDATE name = VALUES(name);
This will:
Insert rows that aren't duplicates w/ resp. to the UNIQUE indexes
Update the name column for the others (when the new value is different)
Caveat: the comments in the documentation on the ON DUPLICATE syntax says that the mysql_affected_rows() result afterwards isn't reliable.

You need to do nothing, this is default behavior.
If your query selects a row for UPDATE but updated value remains the same, as in:
UPDATE table
SET col = col
, the timestamp also remains the same.
This row doesn't even count as affected, the query will return 0 rows affected.

Related

How to ignore duplicates without unique constraint in Postgres 9.4?

I am currently facing an issue in our old database(postgres 9.4) table which contains some duplicate rows. I want to ensure that no more duplicate rows should be generated.
But I also want to keep the duplicate rows that already has been generated. Due to which I could not apply unique constraint on those columns(multiple column).
I have created a trigger which would check the row if already exists and raise exception accordingly. But it is also failing when concurrent transactions are in processing.
Example :
TAB1
col1 | col2 | col3 |
------------------------------------
1 | A | B | --
2 | A | B | -- already present duplicates for column col2 and col3(allowed)
3 | C | D |
INSERT INTO TAB1 VALUES(4 , 'A' , 'B') ; -- This insert statement will not be allowed.
Note: I cannot use on conflict due to older version of database.
Presumably, you don't want new rows to duplicate historical rows. If so, you can do this but it requires modifying the table and adding a new column.
alter table t add duplicate_seq int default 1;
Then update this column to identify existing duplicates:
update t
set duplicate_seq = seqnum
from (select t.*, row_number() over (partition by col order by col) as seqnum
from t
) tt
where t.<primary key> = tt.<primary key>;
Now, create a unique index or constraint:
alter table t add constraint unq_t_col_seq on t(col, duplicate_seq);
When you insert rows, do not provide a value for duplicate_seq. The default is 1. That will conflict with any existing values -- or with duplicates entered more recently. Historical duplicates will be allowed.
You can try to create a partial index to have the unique constraint only for a subset of the table rows:
For example:
create unique index on t(x) where (d > '2020-01-01');

Unknown system variable 'ROWCOUNT' [duplicate]

Is there an easy way to INSERT a row when it does not exist, or to UPDATE if it exists, using one MySQL query?
Use INSERT ... ON DUPLICATE KEY UPDATE. For example:
INSERT INTO `usage`
(`thing_id`, `times_used`, `first_time_used`)
VALUES
(4815162342, 1, NOW())
ON DUPLICATE KEY UPDATE
`times_used` = `times_used` + 1
I know this is an old question, but the Google lead me here recently so I imagine others come here, too.
#chaos is correct: there is the INSERT ... ON DUPLICATE KEY UPDATE syntax.
However, the original question asked about MySQL specifically, and in MySQL there is the REPLACE INTO ... syntax. IMHO, this command is easier and more straightforward to use for upserts. From the manual:
REPLACE works exactly like INSERT, except that if an old row in the table has the same value as a new row for a PRIMARY KEY or a UNIQUE index, the old row is deleted before the new row is inserted.
Note this is not standard SQL. An example from the manual:
CREATE TABLE test (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
data VARCHAR(64) DEFAULT NULL,
ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
mysql> REPLACE INTO test VALUES (1, 'Old', '2014-08-20 18:47:00');
Query OK, 1 row affected (0.04 sec)
mysql> REPLACE INTO test VALUES (1, 'New', '2014-08-20 18:47:42');
Query OK, 2 rows affected (0.04 sec)
mysql> SELECT * FROM test;
+----+------+---------------------+
| id | data | ts |
+----+------+---------------------+
| 1 | New | 2014-08-20 18:47:42 |
+----+------+---------------------+
1 row in set (0.00 sec)
Edit: Just a fair warning that REPLACE INTO isn't like UPDATE. As the manual says, REPLACE deletes the row if it exists, then inserts a new one. (Note the funny "2 rows affected" in the example above.) That is, it will replace the values of all columns of an existing record (and not merely update some columns.) The behavior of MySQL's REPLACE INTO is much like that of Sqlite's INSERT OR REPLACE INTO. See this question for some workarounds if you only want to update a few columns (and not all columns) if the record already exists.

Why does SQL Server populate new fields in existing rows in some environments and not others?

I am using MS SQL Server 2012. I have this bit of SQL:
alter table SomeTable
add Active bit not null default 1
In some environments the default value is applied to existing rows and in other environments we have to add an update script to set the new field to 1. Naturally I am thinking that the difference is a SQL Server setting but my searches thus far are not suggesting which one. Any suggestions?
Let me know if the values of particular settings are desired.
Edit: In the environments that don't apply the default the existing rows are set to 0, which at least conforms to the NOT NULL.
If you add the column as not null it will be set to the default value for existing rows.
If you add the column as null it will be null despite having a default constraint when added to the table.
For example:
create table SomeTable (id int);
insert into SomeTable values (1);
alter table SomeTable add Active_NotNull bit not null default 1;
alter table SomeTable add Active_Null bit null default 1;
select * from SomeTable;
returns:
+----+----------------+-------------+
| id | Active_NotNull | Active_Null |
+----+----------------+-------------+
| 1 | 1 | NULL |
+----+----------------+-------------+
dbfiddle.uk demo: http://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=c4aeea808684de48097ff44d391c9954
Default value will be applied to existing row to avoid violation of "NOT NULL" constraint.

SQLITE: Transaction to check constraints after commit

I'm currently working on an sqlite table where I have to do the following:
ID | Name | SortHint
---|-------|---------
0 | A | 1
1 | B | 2
2 | C | 3
ID is the primary key and SortHint is a column with the UNIQUE-constaint. What I have to do is to modify the table, for example:
ID | Name | SortHint
---|-------|---------
0 | A | 3
1 | B | 1
2 | C | 2
The Problem: Because of the UNIQUE I can't simply update one row after another. I tried:
BEGIN TRANSACTION;
UPDATE MyTable SET SortHint = 3 WHERE ID= 0;
...
COMMIT;
But the first update query immideatly fails with:
UNIQUE constraint failed: MyTable.SortHint Unable to fetch row
So, is there a way to "disable" the unique constaint for a transaction and only check all of them once the transaction is committed?
Notes:
I can't modify the table
It works if I only use SortHint values that are not already in the table
I know how to "workaround" this problem, but I would like to know if there is a way to do this as described above
One possibility is to drop the unique constraint and then add it again. That is a little bit expensive, though.
Another would be to set the values to negative values:
UPDATE MyTable
SET SortHInt = - SortHint;
UPDATE MyTable
SET SortHint = 3
WHERE ID = 0;
. . .
If you cannot modify the table, you are not able to remove the constraint.
A workaround could be to change the SortHint to a range that is not in use.
For example you could add 10,000 to all of them. Commit.
Then change to the right number at once which have become free now.
Maybe test afterwards that no numbers of 10,000 or higher exist anymore.

MySQL: Multiple Inserts for a single column

I'm looking for a way to do multiple row inserts when I'm only inserting data for a single column.
Here is the example table:
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | tinyint(4) | NO | PRI | NULL | auto_increment |
| name | varchar(40) | NO | UNI | NULL | |
+-------+-------------+------+-----+---------+----------------+
I want to be able to insert something like ('admin', 'author', 'mod', 'user', 'guest') into the name column for each row.
The MySQL documentation shows that multiple inserts should be in the format:
INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);
However my statement ends up looking like this:
INSERT INTO User_Role(name) VALUES ('admin','author','mod','user','guest');
And I get the following:
ERROR 1136 (21S01): Column count doesn't match value count at row 1
Meaning that it thinks I'm trying to do a single row insert.
I'm not sure if I'm just missing something simple here, but I don't see anything in particular in the MySQL docs for this use case.
your syntax is a bit off. put parentheses around each data "set" (meaning a single value in this case) that you are trying to insert.
INSERT INTO User_Roll(name) VALUES ('admin'), ('author'), ('mod'), ('user'), ('guest');
I will advise you Don't put multiple values in a column.
make a new table:
INSERT INTO table_name (id, name) VALUES (1, 'name1'), (1, 'name2'), (1, 'name3'), (1, 'name4');