CONSTRAINT to restrict DUPLICATE values among 2 Columns - mysql

I need to put a constraint which will restrict DUPLICATE entries in combination of 2 columns.
So I have a customers table, with the below mentioned columns
id, first_name, last_name, date_of_birth, gender, email_address, primary_number, secondary_number.
What I am expecting is to have primary_number value unique for 2 columns. i.e. primary_number & secondary_number.
Eg.
primary_number
secondary_number
123456789
987654321
**********
123456789
987654321
**********
So, "123456789" should not be allowed in secondary_number, if it is already available in primary_number
As well, "987654321" should not be allowed in primary_number, if it already available in secondary_number

If your MySQL version is 8.0.17 or higher then you may use unique multivalued index.
DEMO
CREATE TABLE test (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
val1 INT UNSIGNED NOT NULL,
val2 INT UNSIGNED NOT NULL,
UNIQUE vals( (CAST(JSON_ARRAY(val1, val2) AS UNSIGNED ARRAY)) )
);
INSERT INTO test (val1, val2) VALUES
(123,234), (345,456);
Records: 2 Duplicates: 0 Warnings: 0
INSERT INTO test (val1, val2) VALUES
(123,567);
Duplicate entry '[123, 56' for key 'test.vals'
INSERT INTO test (val1, val2) VALUES
(678,345);
Duplicate entry '[345, 67' for key 'test.vals'
fiddle

I understand your question in that way only "pairs" like for example 123456789, 987654321 and 987654321, 123456789 should pre prevented by the unique constraint. If the row 123456789, 987654321 already exists, I assume rows like 111111111, 987654321 or 123456789,222222222 should still be allowed.
If this is correct so far and if your two columns are numbers, we can use LEAST and GREATEST:
ALTER TABLE yourtable
ADD CONSTRAINT uniqueNumbers UNIQUE KEY
((LEAST(primary_number, secondary_number)),
(GREATEST(primary_number, secondary_number)));
This will only prevent such duplicated "pairs".
Try out here
If this assumption is incorrect and also the other rows should be prevented, I would use a trigger here rather than constraints or use Akina's idea if possible.

Related

EMPTY TABLE Duplicate entry '1' for key 'PRIMARY'

I have a strange problem with my MariaDB database. I create an empty table with the following code:
drop table if exists Subject;
CREATE TABLE Subject (
id integer primary key auto_increment,
code varchar(100) unique not null,
name text not null
);
Query executed OK, 0 rows affected.
I try to insert some data into the table:
INSERT INTO Subject (id, code, name) VALUES
(0,'KMI/AIdb/PHW/15','Počítačový hardvér'),
(1,'KMI/AIdb/DBA/15','Tvorba databázových aplikácií'),
(2,'KMI/SPRVdb/INF/16','Informatika a základy správy databáz'),
(3,'KMI/AIdb/PR4/15','Programovanie 4 - Objektové programovanie'),
(4,'KMI/AIdb/DBS/15','Databázové informačné systémy');
Error in query (1062): Duplicate entry '1' for key 'PRIMARY'
If I run the same query one more time:
INSERT INTO Subject (id, code, name) VALUES
(0,'KMI/AIdb/PHW/15','Počítačový hardvér'),
(1,'KMI/AIdb/DBA/15','Tvorba databázových aplikácií'),
(2,'KMI/SPRVdb/INF/16','Informatika a základy správy databáz'),
(3,'KMI/AIdb/PR4/15','Programovanie 4 - Objektové programovanie'),
(4,'KMI/AIdb/DBS/15','Databázové informačné systémy');
Query executed OK, 5 rows affected.
I believe it has something to do with the auto_increment, but I have a huge database dump that I would like to insert. Is this a bug, or is this an expected behavior?
AUTO_INCREMENT attribute can be used to generate a unique identity for new rows.
You can also explicitly assign 0 to the column to generate sequence numbers unless the NO_AUTO_VALUE_ON_ZERO SQL mode is enabled.
Read here for more details
The first insert created id=1. This is because "0" (or NULL) is treated specially to mean "give me the next id". Then the second row tried to explicitly insert id=1 and got a "duplicate".
Did your dump include a row with id=0, as you imply in a Comment. That sounds wrong.
Using id autoincrement don't insert id
INSERT INTO Subject (code, name) VALUES
('KMI/AIdb/PHW/15','Počítačový hardvér'),
('KMI/AIdb/DBA/15','Tvorba databázových aplikácií'),
('KMI/SPRVdb/INF/16','Informatika a základy správy databáz'),
('KMI/AIdb/PR4/15','Programovanie 4 - Objektové programovanie'),
('KMI/AIdb/DBS/15','Databázové informačné systémy');
overall don't insert 0 for id

MySQL - INSERT without duplicating rows (without informing the PK)

I have to insert a few values into a mysql table, but only if there are no other (almost) equal rows, for example:
TABLE T
KEY | COL1 | COL2
1 | abc | 123
2 | def | 456
The KEY column uses auto increment, and I don't inform it on the statement, like this:
INSERT INTO T (COL1, COL2) VALUES (abc, 123)
The statement above have the same values as the first row. How can I inform mysql that I don't want to insert if the row is a duplicate like that?
I googled for some solutions and found INSERT IGNORE, ..ON DUPLICATE KEY UPDATE and REPLACE in which I would have to inform the PK, but I don't know it (without using a extra query).
you can do like...
> INSERT INTO memos(id,text)
> SELECT 5, 'text to insert'
> WHERE NOT EXISTS(SELECT 1 FROM memos WHERE id = 5 AND text = 'text to insert');
ON DUPLICATE KEY and INSERT IGNORE will work with any unique index, not just the primary key. So you can add a unique index for these columns:
ALTER TABLE T
ADD UNIQUE INDEX (col1, col2);
Making it a multi-column index means that the combination has to be unique, even though each column can be duplicated individually.
if not exists(select * from t where col1 = "abc" and col2 ="123")then
insert into t (col1,col2) values("abc",123 );
end if;

Insert into SQL if entry does not exist

I want to insert a row into my database table if the value of the first column does not exist in the table.
Example:
Name Value1 Value2
------------------------
John 2 3
Max 4 6
Alex 0 0
Now I want to insert a new person with the values 0 and 0 but only if the person does not exist. For example if I tried to insert John it would not do anything. All of this should happen in one single query.
Can anyone help?
Regards, Max
You can create a unique index on table(name) and then use insert ignore or insert on duplicate key update:
create unique index unq_t_name on t(name);
insert into t(name, value1, value2)
values ($Name, $value1, $value2)
on duplicate key update name = values(name);
The on duplicate key is a non-operation -- it does nothing if the name is already in the database.
Create an index on Name, make it unique. Thereafter you will not be able to add records where the name is already in there.

MySQL insert on duplicate update for non-PRIMARY key

I am little confused with insert on duplicate update query.
I have MySQL table with structure like this:
record_id (PRIMARY, UNIQUE)
person_id (UNIQUE)
some_text
some_other_text
I want to update some_text and some_other_text values for person if it's id exists in my table.person or insert new record in this table otherwise. How it can be done if person_id is not PRIMARY?
You need a query that check if exists any row with you record_id (or person_id). If exists update it, else insert new row
IF EXISTS (SELECT * FROM table.person WHERE record_id='SomeValue')
UPDATE table.person
SET some_text='new_some_text', some_other_text='some_other_text'
WHERE record_id='old_record_id'
ELSE
INSERT INTO table.person (record_id, person_id, some_text, some_other_text)
VALUES ('new_record_id', 'new_person_id', 'new_some_text', 'new_some_other_text')
Another better approach is
UPDATE table.person SET (...) WHERE person_id='SomeValue'
IF ROW_COUNT()=0
INSERT INTO table.person (...) VALUES (...)
Your question is very valid. This is a very common requirement. And most people get it wrong, due to what MySQL offers.
The requirement: Insert unless the PRIMARY key exists, otherwise update.
The common approach: ON DUPLICATE KEY UPDATE
The result of that approach, disturbingly: Insert unless the PRIMARY or any UNIQUE key exists, otherwise update!
What can go horribly wrong with ON DUPLICATE KEY UPDATE? You insert a supposedly new record, with a new PRIMARY key value (say a UUID), but you happen to have a duplicate value for its UNIQUE key.
What you want is a proper exception, indicating that you are trying to insert a duplicate into a UNIQUE column.
But what you get is an unwanted UPDATE! MySQL will take the conflicting record and start overwriting its values. If this happens unintentionally, you have mutilated an old record, and any incoming references to the old record are now referencing the new record. And since you probably won't tell the query to update the PRIMARY column, your new UUID is nowhere to be found. If you ever encounter this data, it will probably make no sense and you will have no idea where it came from.
We need a solution to actually insert unless the PRIMARY key exists, otherwise update.
We will use a query that consists of two statements:
Update where the PRIMARY key value matches (affects 0 or 1 rows).
Insert if the PRIMARY key value does not exist (inserts 1 or 0 rows).
This is the query:
UPDATE my_table SET
unique_name = 'one', update_datetime = NOW()
WHERE id = 1;
INSERT INTO my_table
SELECT 1, 'one', NOW()
FROM my_table
WHERE id = 1
HAVING COUNT(*) = 0;
Only one of these queries will have an effect. The UPDATE is easy. As for the INSERT: WHERE id = 1 results in a row if the id exists, or no row if it does not. HAVING COUNT(*) = 0 inverts that, resulting in a row if the id is new, or no row if it already exists.
I have explored other variants of the same idea, such as with a LEFT JOIN and WHERE, but they all looked more convoluted. Improvements are welcome.
13.2.5.3 INSERT ... ON DUPLICATE KEY UPDATE Syntax
If you specify ON DUPLICATE KEY UPDATE, and a row is inserted that
would cause a duplicate value in a UNIQUE index or PRIMARY KEY, MySQL
performs an UPDATE of the old row.
Example:
DELIMITER //
DROP PROCEDURE IF EXISTS `sp_upsert`//
DROP TABLE IF EXISTS `table_test`//
CREATE TABLE `table_test` (
`record_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`person_id` INT UNSIGNED NOT NULL,
`some_text` VARCHAR(50),
`some_other_text` VARCHAR(50),
UNIQUE KEY `record_id_index` (`record_id`),
UNIQUE KEY `person_id_index` (`person_id`)
)//
INSERT INTO `table_test`
(`person_id`, `some_text`, `some_other_text`)
VALUES
(1, 'AAA', 'XXX'),
(2, 'BBB', 'YYY'),
(3, 'CCC', 'ZZZ')//
CREATE PROCEDURE `sp_upsert`(
`p_person_id` INT UNSIGNED,
`p_some_text` VARCHAR(50),
`p_some_other_text` VARCHAR(50)
)
BEGIN
INSERT INTO `table_test`
(`person_id`, `some_text`, `some_other_text`)
VALUES
(`p_person_id`, `p_some_text`, `p_some_other_text`)
ON DUPLICATE KEY UPDATE `some_text` = `p_some_text`,
`some_other_text` = `p_some_other_text`;
END//
DELIMITER ;
mysql> CALL `sp_upsert`(1, 'update_text_0', 'update_text_1');
Query OK, 2 rows affected (0.00 sec)
mysql> SELECT
-> `record_id`,
-> `person_id`,
-> `some_text`,
-> `some_other_text`
-> FROM
-> `table_test`;
+-----------+-----------+---------------+-----------------+
| record_id | person_id | some_text | some_other_text |
+-----------+-----------+---------------+-----------------+
| 1 | 1 | update_text_0 | update_text_1 |
| 2 | 2 | BBB | YYY |
| 3 | 3 | CCC | ZZZ |
+-----------+-----------+---------------+-----------------+
3 rows in set (0.00 sec)
mysql> CALL `sp_upsert`(4, 'new_text_0', 'new_text_1');
Query OK, 1 row affected (0.00 sec)
mysql> SELECT
-> `record_id`,
-> `person_id`,
-> `some_text`,
-> `some_other_text`
-> FROM
-> `table_test`;
+-----------+-----------+---------------+-----------------+
| record_id | person_id | some_text | some_other_text |
+-----------+-----------+---------------+-----------------+
| 1 | 1 | update_text_0 | update_text_1 |
| 2 | 2 | BBB | YYY |
| 3 | 3 | CCC | ZZZ |
| 5 | 4 | new_text_0 | new_text_1 |
+-----------+-----------+---------------+-----------------+
4 rows in set (0.00 sec)
SQL Fiddle demo
How about my approach?
Let's say you have one table with a autoincrement id and three text-columns. You want to insert/update the value of column3 with the values in column1 and column2 being a (non unique) key.
I use this query (without explicitly locking the table):
insert into myTable (id, col1, col2, col3)
select tmp.id, 'col1data', 'col2data', 'col3data' from
(select id from myTable where col1 = 'col1data' and col2 = 'col2data' union select null as id limit 1) tmp
on duplicate key update col3 = values(col3)
Anything wrong with that? For me it works the way I want.
A flexible solution should retain the atomicity offered by INSERT ... ON DUPLICATE KEY UPDATE and work regardless of if it's autocommit=true and not depend on a transaction with an isolation level of REPEATABLE READ or greater.
Any solution performing check-then-act across multiple statements would not satisfy this.
Here are the options:
If there tends to be more inserts than updates:
INSERT INTO table (record_id, ..., some_text, some_other_text) VALUES (...);
IF <duplicate entry for primary key error>
UPDATE table SET some_text = ..., some_other_text = ... WHERE record_id = ...;
IF affected-rows = 0
-- retry from INSERT OR ignore this conflict and defer to the other session
If there tends to be more updates than inserts:
UPDATE table SET some_text = ..., some_other_text = ... WHERE record_id = ...;
IF affected-rows = 0
INSERT INTO table (record_id, ..., some_text, some_other_text) VALUES (...);
IF <duplicate entry for primary key error>
-- retry from UPDATE OR ignore this conflict and defer to the other session
If you don't mind a bit of ugliness, you can actually use INSERT ... ON DUPLICATE KEY UPDATE and do this in a single statement:
INSERT INTO table (record_id, ..., some_text, some_other_text) VALUES (...)
ON DUPLICATE KEY UPDATE
some_text = if(record_id = VALUES(record_id), VALUES(some_text), some_text),
some_other_text = if(record_id = VALUES(record_id), VALUES(some_other_text), some_other_text)
IF affected-rows = 0
-- handle this as a unique check constraint violation
Note: affected-rows in these examples mean affected rows and not found rows. The two can be confused because a single parameter switches which of these values the client is returned.
Also note, if some_text and some_other_text are not actually modified (and the record is not otherwise changed) when you perform the update, those checks on affected-rows = 0 will misfire.
I came across this post because I needed what's written in the title, and I found a pretty handy solution, but no one mentioned it here, so I thought of pasting it here. Note that this solution is very handy if you're initiating your database tables. In this case, when you create your corresponding table, define your primary key etc. as usual, and for the combination of columns you want to be unique, simply add
UNIQUE(column_name1,column_name2,...)
at the end of your CREATE TABLE statement, for any combination of the specified columns you want to be unique. Like this, according to this page here, "MySQL uses the combination of values in both column column_name1 and column_name2 to evaluate the uniqueness", and reports an error if you try to make an insert which already has the combination of values for column_name1 and column_name2 you provide in your insert. Combining this way of creating a database table with the corresponding INSERT ON DUPLICATE KEY syntax appeared to be the most suitable solution for me. Just need to think of it carefully before you actually start using your table; when setting up your database tables.
For anyone else, like me, who is a DB noob....the above things didn't work for me. I have a primary key and a unique key... And I wanted to insert if unique key didn't exist. After a LOT of Stack Overflow and Google searching, I found not many results for this... but I did find a site that gave me a working answer: https://thispointer.com/insert-record-if-not-exists-in-mysql/
And for ease of reading here is my answer from that site:
INSERT INTO table (unique_key_column_name)
SELECT * FROM (SELECT 'unique_value' AS unique_key_column_name) AS temp
WHERE NOT EXISTS (
SELECT unique_key_column_name FROM table
WHERE unique_key_column_name = 'unique_value'
) LIMIT 1;
Please also note the ' marks are wrapped around for me because I use string in this case.

MYSQL to UPDATE table if row with 2 specific columns exist or INSERT new row if it does not exist

I have a MYSQL table that looks as follows:
id id_jugador id_partido team1 team2
1 2 1 5 2
2 2 2 1 1
3 1 2 0 0
I need to create a query to either INSERT new rows in the table or UPDATE the table. The condition is based on id_jugador and id_partido, meaning that if I wanted to insert id_jugador = 2 and id_partido = 1, then it should just UPDATE the existing row with the new team1 and team2 values I am sending. And dont duplicate the row.
However, if I have an entry id_jugador=2 and id_partido=3, since this combination does not exist yet, it should add the new row.
I read about the REPLACE INTO but it seems to be unable to check combination of UNIQUE KEYS.
If you have a UNIQUE KEY defined on the two columns (id_jugador, id_partido), then you can use:
INSERT ... ON DUPLICATE KEY ...
e.g.
INSERT INTO mytable (id_jugador, id_partido, team1, team2)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY
UPDATE team1 = VALUES(team1)
, team2 = VALUES(team2)
(Obviously, I'm assuming id is an AUTO_INCREMENT PRIMARY KEY, or there's a BEFORE INSERT trigger that generates a value for id when it's not provided.)
MySQL will attempt the INSERT (using up an auto_increment id value). If the INSERT succeeds, MySQL is done, it's just a regular insert. But if the INSERT throws a DUPLICATE KEY exception, then MySQL will perform an update action, equivalent to:
UPDATE mytable SET team1 = ?, team2 = ? WHERE id_jugador = ? AND id_partido = ?
Note that there can be multiple UNIQUE KEY constraints defined on a table. For the predicates of the UPDATE action, MySQL will use the columns/values of whichever unique (or primary) key is identified in the DUPLICATE KEY exception.
Also note that MySQL does actually attempt the INSERT, so MySQL does use up an AUTO_INCREMENT value, even when the INSERT throws a DUPLICATE KEY exception.
I normally avoid a REPLACE, because I usually have a lot of foreign keys, and I don't want a DELETE action. The DELETE action performed by REPLACE is a "real" DELETE; if there are foreign key references, the DELETE rule associated with the foreign key will be obeyed... the delete will fail if the DELETE rule is RESTRICT or NO ACTION and referencing rows exist, or if the DELETE rule is CASCADE or SET NULL, the referencing rows will be deleted or updated. I also believe any BEFORE/AFTER DELETE triggers will also be fired.
Apart from those options, you'd have to run two separate statements.
When I've needed to avoid an INSERT without having a UNIQUE KEY defined, I will typically do something like:
INSERT INTO mytable (id_jugador, id_partido, team1, team2)
SELECT i.*
FROM (SELECT ? AS id_jugador, ? AS id_partido, ? AS team1, ? AS team2) i
LEFT
JOIN mytable s
ON s.id_jugador <=> i.id_jugador
AND s.id_partido <=> i.id_partido
WHERE s.id IS NOT NULL
After the statement executes, test the number of affected rows. If it's zero, we know that the row was not inserted, so we can proceed with an UPDATE.