How to do unique constraint works with NULL value in MySQL - mysql

I am looking for how to implement unique constraints with NULL check.
MySQL shouldn't allow multiple null value.
Employee:
id | name
---|-----
1 | null
2 | null -> should give error during inserting 2nd row.

No, MySQL is doing the right thing, according to the SQL-99 specification.
https://mariadb.com/kb/en/sql-99/constraint_type-unique-constraint/
A UNIQUE Constraint makes it impossible to COMMIT any operation that
would cause the unique key to contain any non-null duplicate values.
(Multiple null values are allowed, since the null value is never equal
to anything, even another null value.)
If you use a UNIQUE constraint but don't want multiple rows with NULL, declare the columns as NOT NULL and prohibit any row from having NULL.

MySQL 5.7 does allow for a workaround:
mysql> CREATE TABLE `null_test` (
-> `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-> `const` varchar(255) NOT NULL DEFAULT '',
-> `deleted_at` datetime DEFAULT NULL,
-> PRIMARY KEY (`id`)
-> ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.03 sec)
With soft deletes, it would be nice if you could have just one row with a with a deleted_at = NULL per constraint.
mysql> ALTER TABLE `null_test` ADD `vconst` int(1) GENERATED ALWAYS AS (((NULL = `deleted_at`) or (NULL <=> `deleted_at`))) VIRTUAL;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
So I created a virtual column that will flip from 1 to null when deleted_at gets set.
mysql> ALTER TABLE `null_test` ADD UNIQUE KEY `nullable_index` (`const`,`vconst`);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
Instead of including deleted_at to the unique constraint add the virtual column, vconst.
mysql> INSERT INTO `null_test` SET `const` = 'Ghost';
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM `null_test` WHERE `const` = 'Ghost';
+--------+-------+------------+--------+
| id | const | deleted_at | vconst |
+--------+-------+------------+--------+
| 999901 | Ghost | NULL | 1 |
+--------+-------+------------+--------+
1 row in set (0.01 sec)
No need to insert the vconst (but you cannot, anyhow).
mysql> INSERT INTO `null_test` SET `const` = 'Ghost';
ERROR 1062 (23000): Duplicate entry 'Ghost-1' for key 'nullable_index'
Inserting it again throws the Duplicate entry error.
mysql> UPDATE `null_test` SET `deleted_at` = NOW() WHERE `const` = 'Ghost';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Same with setting delete_at, no need to touch vconst, it will flip automatically.
mysql> SELECT * FROM `null_test` WHERE `const` = 'Ghost';
+--------+-------+---------------------+--------+
| id | const | deleted_at | vconst |
+--------+-------+---------------------+--------+
| 999901 | Ghost | 2017-02-16 22:07:45 | NULL |
+--------+-------+---------------------+--------+
1 row in set (0.00 sec)
mysql> INSERT INTO `null_test` SET `const` = 'Ghost';
Query OK, 1 row affected (0.00 sec)
Now you are free to insert a new row with the same constraints!
mysql> SELECT * FROM `null_test` WHERE `const` = 'Ghost';
+--------+-------+---------------------+--------+
| id | const | deleted_at | vconst |
+--------+-------+---------------------+--------+
| 999901 | Ghost | 2017-02-16 22:07:45 | NULL |
| 999903 | Ghost | NULL | 1 |
+--------+-------+---------------------+--------+
2 rows in set (0.01 sec)
In this case, depending on how much you soft delete, setting deleted_at, you might want to include deleted_at to the index, or a new index with it, but I will let my load tests decide.

alter table yourtable add column `virtual_null` varchar(20) GENERATED ALWAYS AS (if(isnull(`your_nullable_column`),'null',`your_nullable_column`))) VIRTUAL;
alter table yourtable add constraint unique(virtual_null);
Make this and be happy, behind the scenes mysql's null is a hash value. Because that its impossible compare two null values...
Sorry by poor english, good luck

Related

distinguish in information_schema DB: column with default NULL vs. column with no DEFAULT

i use mysql 5.7.
if i look only into 'information_schema' database, is there a way to distinguish a column with default NULL and a column without default?
here's my table:
mysql> CREATE TABLE defaults (default_null varchar(100) DEFAULT NULL, no_default varchar(100)) ENGINE=InnoDB;
Query OK, 0 rows affected (0.02 sec)
mysql> ALTER TABLE defaults ALTER COLUMN no_default DROP DEFAULT;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SHOW CREATE TABLE defaults;
+----------+------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+----------+------------------------------------------------------------------------------------------------------------------------------------------+
| defaults | CREATE TABLE `defaults` (
`default_null` varchar(100) DEFAULT NULL,
`no_default` varchar(100)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+----------+------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> INSERT INTO defaults SET no_default = 'foo';
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO defaults SET default_null = 'bar';
ERROR 1364 (HY000): Field 'no_default' doesn't have a default value
mysql>
no_default and default_null columns are different, but in information_schema.columns table they are the same:
mysql> SELECT column_name, is_nullable, IFNULL(column_default, 'real NULL') FROM information_schema.columns WHERE table_name = 'defaults';
+--------------+-------------+-------------------------------------+
| column_name | is_nullable | IFNULL(column_default, 'real NULL') |
+--------------+-------------+-------------------------------------+
| default_null | YES | real NULL |
| no_default | YES | real NULL |
+--------------+-------------+-------------------------------------+
2 rows in set (0.01 sec)
mysql>
This behaviour has every Version if strict sql mode is enabled.
If strict SQL mode is enabled, an INSERT statement generates an error if it does not specify an explicit value for every column that has no default value. See Section 5.1.10, “Server SQL Modes”.
You can find it under https://dev.mysql.com/doc/refman/5.7/en/insert.html

MySQL ENUM to VARCHAR conversion issue

I have a table "temp_enum_test1" with data type "enum('IE','IS')". When I try to ALTER the table by changing data type into VARCHAR from 'ENUM', getting duplicate entry error. It is accepting records in ENUM type. Even when I query the table I am getting unique rows. Can anyone please help me here. Below are the schema and my approach.
mysql> CREATE TABLE temp_enum_test1 (
-> r_id int(11) NOT NULL,
-> r_type enum('IE','IS'),
-> UNIQUE KEY uk_temp_enum_test1 (r_id,r_type)
-> );
Query OK, 0 rows affected (0.38 sec)
mysql> insert into temp_enum_test1 values(1,'IE');
Query OK, 1 row affected (0.07 sec)
mysql> insert into temp_enum_test1 values(1,'IS');
Query OK, 1 row affected (0.05 sec)
mysql> select * from temp_enum_test1;
+------+--------+
| r_id | r_type |
+------+--------+
| 1 | IE |
| 1 | IS |
+------+--------+
2 rows in set (0.00 sec)
mysql> alter table temp_enum_test1 change column r_type r_type varchar(30);
ERROR 1062 (23000): Duplicate entry '1-I' for key 'uk_temp_enum_test1'
mysql>
The problem seems to be with the create table query. The table has been created with 'UNIQUE KEY' that is causing the error while altering the table. If you can use
PRIMARY KEY (`r_id`)
instead of ...
UNIQUE KEY uk_temp_enum_test1 (r_id,r_type)
Complete CREATE command as below;
CREATE TABLE IF NOT EXISTS temp_enum_test1 (
`r_id` int(11) NOT NULL AUTO_INCREMENT,
`r_type` enum('IE','IS') NOT NULL,
PRIMARY KEY (`r_id`)
)

Automatically generating 6 digit unique number in MySQL

I have a table named 'users' with the follwing columns
id
email
password
pin
I want the 'pin' to be auto-filled with a unique 6 digit number every time a new record is added. It would be of a great help if anyone could suggest a good approach for this problem.
Define the PIN column as
pin MEDIUMINT NOT NULL AUTO_INCREMENT
Use the zerofill option for your int field. the Mysql fills it for your.
CREATE TABLE `int6` (
`id` int(6) unsigned zerofill NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
sample
MariaDB [l]> drop table int6;
Query OK, 0 rows affected (0.00 sec)
MariaDB [l]> CREATE TABLE `int6` (
-> `id` int(6) unsigned zerofill NOT NULL AUTO_INCREMENT,
-> PRIMARY KEY (`id`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.02 sec)
MariaDB [l]> insert into int6 VALUES (1),(2),(3);
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
MariaDB [l]> select * from int6;
+--------+
| id |
+--------+
| 000001 |
| 000002 |
| 000003 |
+--------+
3 rows in set (0.00 sec)
MariaDB [l]>

MySql silently ignores "references" keyword on table creation

mysql> create database test;
Query OK, 1 row affected (0.01 sec)
mysql> use test;
Database changed
mysql> create table one (id int not null primary key);
Query OK, 0 rows affected (0.03 sec)
mysql> -- here is the problem
mysql> create table two (oneid int not null references one(id));
Query OK, 0 rows affected (0.02 sec)
mysql> -- here are the first signs of issues!!!!
mysql> show create table two;
+-------+----------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+----------------------------------------------------------------------------------------+
| two | CREATE TABLE `two` (
`oneid` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+-------+----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> -- here is the issue: an insert with no reference
mysql> insert into two values (-12);
Query OK, 1 row affected (0.01 sec)
mysql> select * from two;
+-------+
| oneid |
+-------+
| -12 |
+-------+
1 row in set (0.00 sec)
mysql> -- if you want to know:
mysql> SHOW Variables WHERE Variable_name='foreign_key_checks';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| foreign_key_checks | ON |
+--------------------+-------+
1 row in set (0.00 sec)
After my research, the only question here is: why MySql do not reject table two creation because of its not valid syntax and instead it silently create the table without the foreign key reference?
Just for completeness here is the correct syntax for MySql.
mysql> create table three(oneid int not null, CONSTRAINT whatEverName FOREIGN KEY (oneid) REFERENCES one(id));
Query OK, 0 rows affected (0.04 sec)
MySQL parses but ignores “inline REFERENCES specifications” (as defined in the SQL standard) where the references are defined as part of the column specification. MySQL accepts REFERENCES clauses only when specified as part of a separate FOREIGN KEY specification.
You can go here and read more yourself...

how to define null as integer value on mysql server?

I need to allow saving null as integer value on mysql server, how can I do this?
Assuming your question is about the "Incorrect integer value" error message...
mysql> CREATE TABLE `foo` (
-> `foo_id` INT(10) NOT NULL AUTO_INCREMENT,
-> `foo_size` INT(10) NOT NULL DEFAULT '0',
-> PRIMARY KEY (`foo_id`)
-> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO foo (foo_size) VALUES ('');
ERROR 1366 (HY000): Incorrect integer value: '' for column 'foo_size' at row 1
... that means that your server is running in strict mode (which is actually good). You can disable it:
mysql> SET ##session.sql_mode='';
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO foo (foo_size) VALUES ('');
Query OK, 1 row affected, 1 warning (0.00 sec)
mysql> SHOW WARNINGS;
+---------+------+------------------------------------------------------------+
| Level | Code | Message |
+---------+------+------------------------------------------------------------+
| Warning | 1366 | Incorrect integer value: '' for column 'foo_size' at row 1 |
+---------+------+------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM foo;
+--------+----------+
| foo_id | foo_size |
+--------+----------+
| 1 | 0 |
+--------+----------+
1 row in set (0.00 sec)
... and your error will be downgraded to warning.
I found out how:
I hided the line below then restarted the sql server and it worked.
# Set the SQL mode to strict
sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
Note this line is found in my.ini file.
Set the field to not null and set its default to zero:
CREATE TABLE `t` (
`foo` BIGINT NOT NULL DEFAULT 0
);
Value of foo will be zero if you send it NULL.