Conditional unique constraint in mariadb

Viewed 21

I have this table in mariadb

create or replace table test_table (
    col_key int primary key,
    col_a int not null,
    col_b int not null check(col_b in (0, 1))
);

There is an additional constraint that the pair (col_a, col_b) must be unique only if col_b = 1

For example, you are allowed to have

| col_key | col_a | col_b |
| ------- | ----- | ----- |
|    1    |   1   |   0   |
|    2    |   1   |   0   |

But not

| col_key | col_a | col_b |
| ------- | ----- | ----- |
|    1    |   1   |   1   |
|    2    |   1   |   1   |

I think of 2 approaches:

  1. Since col_b only has 2 values, I can take advantage of the fact that in mariadb null can by pass unique check. So instead of 0 and 1, I change the definition of the table to this and treat null as 0 in application code.

    create or replace table test_table (
        col_key int primary key,
        col_a int not null,
        col_b int check(col_b is null or col_b = 1),
        unique (col_a, col_b)
    );
    

    The upside is I have the database handle the check for me. The downside is the application code become a bit complex and the code tightly couple to mariadb's implementation.

  2. When update or insert, write query like this

    update test_table t
    set t.col_b = 1
    where t.col_key = 2 
        and not exists (select 1 from test_table t2 where t2.col_a = 1 and t2.col_b = 1)
    

    The upside is the application code actually works with 0 and 1 values. The downside is... I unsure if this work or not. Does the database lock the table when it run the update query? Is there any chance that some other process inserts a row with col_b = 1 after the subquery returns the result?

1 Answers

According to your question, the combination of cola and colb is unique, if colb has the value 1. This condiition can be already defined in the table definition:

CREATE TABLE test_table (
  col_key int primary key,
  col_a int not null,
  col_b int check(col_b is null or col_b = 1),
  unique (col_a, col_b)
)

Since the unique index allows multiple NULL values, multiple same combinations of col_a and col_b are allowed, as long the value of col_b is NULL.

For updating you don't need a subquery, since the server checks this condition itself. For not raising an error, just use UPDATE IGNORE

UPDATE IGNORE test_table SET col_b=1 WHERE col_key=2

With the first solution the logic is defined in the database (and doesn't need to be defined in your application) and it is much faster since it can use an index, while the subquery performs a table scan.

MariaDB [test]> insert into test_table select seq,1,NULL from seq_1_to_1000;
Query OK, 1000 rows affected (0,081 sec)
Records: 1000  Duplicates: 0  Warnings: 0

MariaDB [test]> explain update test_table set col_b=1 where col_key=18 and not exists (select 1 from test_table t2 where t2.col_a=1 and t2.col_b=1);
+------+-------------+------------+-------+---------------+---------+---------+-------+------+-------------+
| id   | select_type | table      | type  | possible_keys | key     | key_len | ref   | rows | Extra       |
+------+-------------+------------+-------+---------------+---------+---------+-------+------+-------------+
|    1 | PRIMARY     | test_table | const | PRIMARY       | PRIMARY | 4       | const | 1    |             |
|    2 | SUBQUERY    | t2         | ALL   | NULL          | NULL    | NULL    | NULL  | 1000 | Using where |
+------+-------------+------------+-------+---------------+---------+---------+-------+------+-------------+
2 rows in set (0,003 sec)

MariaDB [test]> alter table test_table add unique (col_a, col_b);
Query OK, 0 rows affected (0,079 sec)
Records: 0  Duplicates: 0  Warnings: 0

MariaDB [test]> explain update test_table set col_b=1 where col_key=18;
+------+-------------+------------+-------+---------------+---------+---------+------+------+-------------+
| id   | select_type | table      | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+------+-------------+------------+-------+---------------+---------+---------+------+------+-------------+
|    1 | SIMPLE      | test_table | range | PRIMARY       | PRIMARY | 4       | NULL | 1    | Using where |
+------+-------------+------------+-------+---------------+---------+---------+------+------+-------------+
Related