MySQL UNIQUE CONSTRAINT failing in CREATE TABLE with subsequent INSERT - mysql

Using phpMyAdmin and MySQL v5.5.49 consider:
CREATE TABLE op_sys (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
version VARCHAR(255) NOT NULL,
-- UNIQUE KEY name_version (name, version)
-- CONSTRAINT name_version UNIQUE (name, version)
-- UNIQUE(name, version)
-- CONSTRAINT UNIQUE(name, version)
)ENGINE=InnoDB;
I've tried all four of the commented out attempts to simply stop INSERT INTO sys_op duplicate values for "name" and "version". All four are processed without error.
The insert into:
INSERT INTO op_sys(name, version)
VALUES ('ANDROID','ANDROID');
executes "successfully". ANDROID ANDROID is now a row. Where have I gone wrong or what step am I not aware of? I've checked the MySQL manual and several different posts here that seem to say I'm doing it correctly... Thanks.

You seem to misunderstand what UNIQUE KEY means:
A UNIQUE index creates a constraint such that all values in the index
must be distinct. An error occurs if you try to add a new row with a
key value that matches an existing row. For all engines, a UNIQUE
index permits multiple NULL values for columns that can contain NULL.
If your table has UNIQUE(name, version), then you can do:
INSERT INTO op_sys(name, version) VALUES ('ANDROID','ANDROID');
But, the next time you do it, it will fail because the table already holds a record with the same pair(name, version) as in the record you want to insert.
To prevent inserting a record that has the same value for name and version`, you could use a trigger:
CREATE TRIGGER different_values BEFORE INSERT ON op_sys
FOR EACH ROW BEGIN
DECLARE identical_values CONDITION FOR SQLSTATE '45000';
IF NEW.name = NEW.version THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Identical values for name and version';
END IF;
END;
It will run before each INSERT on table sys_op, and if the name and version fields hold identical values, it will generate an error and the insertion will fail.
The error returned looks like this:
ERROR 1644 (45000): Identical values for name and version
Documentation:
- CREATE INDEX
- CREATE TRIGGER
- SIGNAL

A multi-column unique index prevents you to have the same 2 values for these 2 fields between records. This means, you cannot have 2 records, where name and version are 'ANDROID','ANDROID'. However, a unique index does not prevent these fields from having the same value within a single row.
You either have to implement this control in application level, where you check if the 2 field values are the same and if yes, then do not do the insertion.
In the database layer you could ad a before insert trigger and check the 2 fields' value there and raise a custom error message using the signal command.
But I have such a de ja vu feeling. As if you had asked this question before and you could not do an if() in php...

Related

Constraint that checks there is only one is_primary true in one to many group

In a one-to-many table setup say customer to phone. Where one customer can have many phones.
The table for phone has a column is_primary so that the applications knows which one to use under normal circumstances. There can only be one is_primay that is true and n that can be false.
Is there any constraint in MySQL that can be applied to enforce the above scenario? The application currently protects against this but we wanted to add a block at the db.
A unique constraint does not allow for the n number of false phone. It would make is so only 1 true and 1 false per customer.
There is the idea of a partial unique constraint but I cannot find docs in MySQL for this just postgres.
UNIQUE constraints in MySQL (as per the standard) ignore NULLs. So you could define the column as the following:
create table mytable (
is_primary tinyint null,
check(is_primary = 1),
unique key (is_primary)
);
insert into mytable values (1); -- ok
insert into mytable values (null); -- ok
insert into mytable values (null); -- ok
insert into mytable values (null); -- ok
insert into mytable values (1); -- duplicate
insert into mytable values (2); -- error
MySQL supports CHECK constraints starting at version 8.0.16. If you use an earlier version of MySQL, you would need to enforce this with a trigger, or else just make sure you client code never tries to insert a value other than 1 or null.
Here's another solution inspired by Akina's comment:
create table mytable (
is_primary tinyint null,
check(is_primary in (0,1)),
unique key ((nullif(is_primary, 0)))
);
This uses MySQL 8.0 expression indexes, so you can still insert 0 or 1 into the is_primary column, but 0's will be indexed as if they are NULLs, which means the UNIQUE constraint ignores it.
I can't create a dbfiddle for this, because dbfiddle is still using MySQL 8.0.12, and that's too old to support expression indexes. I tested this on 8.0.28 and it works.

Constant column value in MySQL table

I have a table in MySQL. I'd like to set a column value for a table to be a constant integer. How can I do this?
Unfortunately MySQL does not support SQL check constraints. You can
define them in your DDL query for compatibility reasons but they are
just ignored. You can create BEFORE INSERT and BEFORE UPDATE triggers
which either cause an error or set the field to its default value when
the requirements of the data are not met.
So here you can find a way around through MYSQL TRIGGER.
Sample Table:
DROP TABLE IF EXISTS `constantvaluetable`;
CREATE TABLE `constantvaluetable` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`constValue` int(11) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB;
Trigger:
DROP TRIGGER IF EXISTS trigger_const_check;
delimiter //
CREATE TRIGGER trigger_const_check BEFORE INSERT ON constantvaluetable
FOR EACH ROW
BEGIN
IF NEW.constValue <> 71 THEN
SIGNAL SQLSTATE '45000' SET message_text ='Only allowed value is 71';
END IF;
END //
delimiter ;
Test:
INSERT INTO constantvaluetable(constValue) VALUES(71);
INSERT INTO constantvaluetable(constValue) VALUES(66);
Result:
The first insert statement will succeed.
The second insert statement will fail. And the following error message will be shown:
[Err] 1644 - Only allowed value is 71
Note: Assuming your CONSTANT value is 71.
Do you really want to do this?
Would the following not suffice
Select Field1, field2, field3 , 5 as `ConstantField` from myTable
Although 71's trigger solution is the general purpose approach, since it can be used for more complicated conditions, in your case where you just want to check for a constant value, you can stay closer to database logic and add a foreign key to a table that just contains that one allowed value in it, e.g.
create table tbl_checkconst (constraintvalue int primary key);
insert into tbl_checkconst values (71);
alter table yourtable
add constraint fk_yourtable_constcheck
foreign key (column1)
references tbl_chechconst (constraintvalue);
It will actually add some overhead (since it will need to add an index), but would express your constraint in database logic, and your constant usually has a meaning that is in this way designed into the database model (although it is just 1 value now), and you (and any user with the correct permissions) can easily add more allowed values by adding it to the tbl_checkconst-table without modifying your trigger code.
And another reason I added it is that I guess you are really actually looking for a foreign key: In one of your comments you said you are trying to create a "double foreign key to a reference table". If I understand that correctly, you might want to use a composite foreign key, since you are able to combine columns for a foreign key:
alter table yourtable
add constraint fk_yourtable_col1col2
foreign key (column1, column2)
references your_reference_table (refcolumn1, refcolumn2);
You would just set up a CHECK constraint in your table when you set it up. Something like this is all you need in most DBMSs
CREATE someTable
(
someValue int(4) CHECK (someValue = 4)
)
However, with MySQL, CHECK constraints don't behave the same as they do in other DBMSs. The situation is a little more tricky. The answer seems to be here: https://stackoverflow.com/a/14248038/5386243

Inserting into two MySQL related tables with surogate primary keys

In my database, all Primary Keys are surogate. There are some Unique keys, but not always, so the most safe way to access specific row is Primary Key. Many of them use AUTO_INCREMENT. Do I have to lock access to database when inserting into two related table? For example.
create table foo
(
foo_id numeric not null auto_increment,
sth varchar,
PRIMARY KEY(foo_id)
)
create table bar
(
bar_id numeric not null auto_increment,
foo_id numeric not null,
PRIMARY KEY(bar_id),
FOREIGN KEY (foo_id) REFERENCES foo(foo_id)
)
First I insert sth to foo, and then I need foo_id value to insert related stuff into bar. This value I can get from INFORMATION_SCHEMA.TABLES. But what if somebody will add new row into foo before I get the auto_increment value? If all these steps are in stored procedure is there implicitly started transactions which locks all needed resources for one procedure call? Or maybe I have to use explicitly START TRANSACTION. What if I dont use procedure - just sequence of inserts and selects?
Instead of looking in INFORMATION_SCHEMA.TABLE, I would suggest that you use LAST_INSERT_ID.
From the MySQL documentation: The ID that was generated is maintained in the server on a per-connection basis. This means that the value returned by the function to a given client is the first AUTO_INCREMENT value generated for most recent statement affecting an AUTO_INCREMENT column by that client.
This imply that an insert done at the same time on a different connection will not change the value that is returned on your current connection.
Run queries in that sequence:
INSERT INTO foo (sth) VALUES ('TEST');
Than:
INSERT INTO bar (foo_id) VALUES (SELECT LAST_INSERT_ID());

MYSQL: Getting existing primary key when inserting record with duplicate unique key?

I've got a mysql database with a table that has both a auto-increment primary key and unique string valued key (a sha-1 hash).
If I try to add a record that has the same sha-1 hash as an existing record, I just want to get the primary key of the existing record. I can use something like "INSERT ... ON DUPLICATE KEY UPDATE" or "INSERT IGNORE" to prevent an exception when trying to insert a record with a existing hash value.
However, when that happens, I need to retrieve the primary key of the existing record. I can't find a way to do that with a single SQL statement. If it matters, my code is in Java and I'm using JDBC.
Alternatively, I can do it with two statements (either a query followed by an insertion if not found, or a insertion followed by a query if a duplicate key exists). But I presume a single statement would be more efficient.
If I try to add a record that has the same sha-1 hash as an existing
record, I just want to get the primary key of the existing record. I
can use something like "INSERT ... ON DUPLICATE KEY UPDATE" or "INSERT
IGNORE" to prevent an exception when trying to insert a record with a
existing hash value.
If you have an UNIQUE index on a column, no matter what you tried, the RDMS will not allow duplicates in that column (except for the NULL value).
As you said, there is solution to prevent "error" if this appends. Probably INSERT IGNORE in your case.
Anyway, INSERT and UPDATE modify the database. MySQL never return values for these statements. The only way to read your DB is to use a SELECT statement.
Here the "workaround" is simple, since you have an UNIQUE column:
INSERT IGNORE INTO tbl (pk, sha_key) VALUES ( ... ), ( ... );
SELECT pk, sha_key FROM tbl WHERE sha_key IN ( ... );
-- ^^^
-- Here the list of the sha1 keys you *tried* to insert
Actually, INSERT...ON DUPLICATE KEY UPDATE is exactly the right statement to use in your situation. When you use ON DUPLICATE, if the insert happens without duplicate, JDBC returns count of 1 and the ID of the newly inserted row. If the action taken is an update due to duplicate, JDBC returns count of 2 and both the ID of the original row AND the newly generated ID, even though the new ID is never actually inserted into the table.
You can get the correct key by calling PreparedStatement.getGeneratedKeys(). The first key is pretty much always the one you are interested in. For this statement:
INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=3;
You can get the inserted or updated ID by calling:
Long key;
ResultSet keys = preparedStatement.getGeneratedKeys();
if (keys.next())
key = keys.getLong("GENERATED_KEY");

Unique index not enforced on multiple columns - different behaviour MySQL vs. Oracle

This simple script:
create table test (a integer not null, b integer, c integer not null);
create unique index test1 on test (a, b, c);
insert into test values(1, null, 1);
insert into test values(1, null, 1);
select * from test;
runs successfully with MySQL and fails with ORA-0001 "unique constraint violated" with Oracle.
I am unsure what the standard says about unique index on multiple null columns but MySQL should probably behave similarly as Oracle.
See also http://lists.mysql.com/mysql/183630
Alexandre.
MySQL, by design, allows multiple NULL values in a column which has a UNIQUE index on it.
The BDB storage engine on MySQL was an exception, and it did not allow multiple NULL values in a coulmn, having UNIQUE index on it.
On the other hand, Oracle behaved differently with regards to NULL values. When you create a UNIQUE index on a single column in Oracle, it will allow you to have multiple NULL values, since NULL basically means an Unknown value, so two NULL values can't be compared to one another. More than that, NULL values are not stored in Index in case of Oracle. What this means is, when you create a UNIQUE index on multiple columns in Oracle, where two columns are NOT NULL and one column is NULLABLE, it will not allow you insert two records with same values, even though one column contains NULL.
Consider this:
CREATE TABLE test (a NUMBER NOT NULL,
b NUMBER,
c NUMBER NOT NULL
);
INSERT INTO test VALUES (1, NULL, 1);
1 rows inserted.
INSERT INTO test VALUES (1, NULL, 1);
SQL Error: ORA-00001: unique constraint (RQ4151.UQ_TEST) violated
00001. 00000 - "unique constraint (%s.%s) violated"
*Cause: An UPDATE or INSERT statement attempted to insert a duplicate key.
For Trusted Oracle configured in DBMS MAC mode, you may see
this message if a duplicate entry exists at a different level.
*Action: Either remove the unique restriction or do not insert the key.
This happens, since Oracle is not storing NULL values in the index, and so tries to compare the NOT NULL column values for uniqueness in the index, which fails, causing the error to be flagged.