MySQL Two-Sided UNIQUE INDEX for Two Columns – How to Implement

duplicationMySQLprimary-keyunique-constraint

Creating a table with composite PRIMARY KEY or using UNIQUE INDEX for two columns guarantee uniqueness of col1, col2. Is there a tricky approach to make the reverse order of two columns UNIQUE too (col2, col1)?

For example

PRIMARY KEY (col1, col2)

OR

UNIQUE INDEX (col1, col2)

If we have col1=33 && col2=54; how we can avoid INSERT of col1=54 && col2=33?

Best Answer

You may want to try creating a trigger that will check for the presence of (col1,col2) existing as (col2,col1)

Here is an example:

use test
drop table if exists ali;
create table ali
(
    col1 int not null,
    col2 int not null,
    primary key (col1,col2)
);
DELIMITER $$
CREATE TRIGGER ali_bi BEFORE INSERT ON ali FOR EACH ROW 
BEGIN 
    DECLARE found_count,newcol1,newcol2,dummy INT;
    SET newcol1 = NEW.col1;
    SET newcol2 = NEW.col2;
    SELECT COUNT(1) INTO found_count FROM ali
    WHERE col1 = newcol2 AND col2 = newcol1;
    IF found_count = 1 THEN
        SELECT 1 INTO dummy FROM information_schema.tables;
    END IF;
END; $$ 
DELIMITER ;
INSERT INTO ali VALUES (1,2);
INSERT INTO ali VALUES (3,4);
INSERT INTO ali VALUES (2,1);
INSERT INTO ali VALUES (4,3);
SELECT * FROM ali;

The trigger is designed to break on purpose when FOUND_COUNT is 1.

Here is the sample executed:

mysql> use test
Database changed
mysql> drop table if exists ali;
Query OK, 0 rows affected (0.03 sec)

mysql> create table ali
    -> (
    ->     col1 int not null,
    ->     col2 int not null,
    ->     primary key (col1,col2)
    -> );
Query OK, 0 rows affected (0.06 sec)

mysql> DELIMITER $$
mysql> CREATE TRIGGER ali_bi BEFORE INSERT ON ali FOR EACH ROW
    -> BEGIN
    ->     DECLARE found_count,newcol1,newcol2,dummy INT;
    ->     SET newcol1 = NEW.col1;
    ->     SET newcol2 = NEW.col2;
    ->     SELECT COUNT(1) INTO found_count FROM ali
    ->     WHERE col1 = newcol2 AND col2 = newcol1;
    ->     IF found_count = 1 THEN
    ->         SELECT 1 INTO dummy FROM information_schema.tables;
    ->     END IF;
    -> END; $$
Query OK, 0 rows affected (0.07 sec)

mysql> DELIMITER ;
mysql> INSERT INTO ali VALUES (1,2);
Query OK, 1 row affected (0.07 sec)

mysql> INSERT INTO ali VALUES (3,4);
Query OK, 1 row affected (0.06 sec)

mysql> INSERT INTO ali VALUES (2,1);
ERROR 1172 (42000): Result consisted of more than one row
mysql> INSERT INTO ali VALUES (4,3);
ERROR 1172 (42000): Result consisted of more than one row
mysql> SELECT * FROM ali;
+------+------+
| col1 | col2 |
+------+------+
|    1 |    2 |
|    3 |    4 |
+------+------+
2 rows in set (0.00 sec)

mysql>

Give it a Try !!!

CAVEAT This does not work on bulk INSERTs. Only when inserting one row at a time.

I have used this technique and suggested it in other DBA StackExchange Questions

UPDATE 2012-02-29 11:46 EDT

I tried to INSERT the same four rows again.

INSERT INTO ali VALUES (1,2);
INSERT INTO ali VALUES (3,4);
INSERT INTO ali VALUES (2,1);
INSERT INTO ali VALUES (4,3);
SELECT * FROM ali;

Here is what I get

mysql> INSERT INTO ali VALUES (1,2);
ERROR 1062 (23000): Duplicate entry '1-2' for key 'PRIMARY'
mysql> INSERT INTO ali VALUES (3,4);
ERROR 1062 (23000): Duplicate entry '3-4' for key 'PRIMARY'
mysql> INSERT INTO ali VALUES (2,1);
ERROR 1172 (42000): Result consisted of more than one row
mysql> INSERT INTO ali VALUES (4,3);
ERROR 1172 (42000): Result consisted of more than one row
mysql> SELECT * FROM ali;
+------+------+
| col1 | col2 |
+------+------+
|    1 |    2 |
|    3 |    4 |
+------+------+
2 rows in set (0.00 sec)

mysql>

This trigger approach work well despite the quirky message.