🐘

PostgreSQL の UNIQUE INDEX で NULL の重複も許可しないようにする

2024/03/16に公開

UNIQUE INDEX での NULL の扱い

UNIQUE INDEXは、デフォルトではNULLを区別できません。
(NULLのレコードを複数入れられてしまう)

たとえば下記のようなテーブルを用意します。

CREATE TABLE sample (
  id serial PRIMARY KEY,
  name text
);

nameカラムにUNIQUE INDEXを設定します。

CREATE UNIQUE INDEX sample_name_idx ON sample (name);

同じ値を入れると、UNIQUE INDEXなので一意制約エラーとなります。

INSERT INTO sample (name) VALUES ('taro');
INSERT INTO sample (name) VALUES ('taro');
postgres=# INSERT INTO sample (name) VALUES ('taro');
INSERT 0 1
postgres=# INSERT INTO sample (name) VALUES ('taro');
ERROR:  duplicate key value violates unique constraint "sample_name_idx"
DETAIL:  Key (name)=(taro) already exists.

NULLはいくつでも入れられます。(NULLの重複は弾けない)

INSERT INTO sample (name) VALUES (NULL);
INSERT INTO sample (name) VALUES (NULL);
postgres=# INSERT INTO sample (name) VALUES (NULL);
INSERT 0 1
postgres=# INSERT INTO sample (name) VALUES (NULL);
INSERT 0 1
postgres=# SELECT * FROM sample;
 id | name
----+------
  1 | taro
  3 |
  4 |
(3 rows)

UNIQUE INDEX で NULL の重複も弾く

PostgreSQL 15 でNULLS NOT DISTINCTというオプションが入りました。

これを使うと、NULLの重複も弾けます。(NULLも他の値と同様に、一つの値として扱われる)

CREATE UNIQUE INDEX sample_name_idx ON sample (name) NULLS NOT DISTINCT;
INSERT INTO sample (name) VALUES (NULL);
INSERT INTO sample (name) VALUES (NULL);
postgres=# INSERT INTO sample (name) VALUES (NULL);
INSERT 0 1
postgres=# INSERT INTO sample (name) VALUES (NULL);
ERROR:  duplicate key value violates unique constraint "sample_name_idx"
DETAIL:  Key (name)=(null) already exists.

どんなときに使えるか

1カラムでNULLの重複も弾きたいみたいなことはあまりないのかもしれない(今までそういうのに遭遇したこと無い)のですが、複合カラムだとそういった時もあるのかもしれません。

たとえば下記のようなテーブルで、

CREATE TABLE sample (
  point1 integer NOT NULL,
  point2 integer,
  point3 integer
);

point1point2point3 には

  • 1, NULL, NULL
  • 1, 1, NULL
  • 1, 1, 1
  • 1, 2, NULL
  • 1, 2, 1

のような感じで、point1から順に設定されるので、それらでユニークにしたいといったような場合です。

NULLS NOT DISTINCTが無いPostgreSQL 14以前は、下記のような部分UNIQUE INDEXを複数組み合わせないと弾けませんでした。

CREATE UNIQUE INDEX sample_idx1 ON sample (point1) WHERE point2 IS NULL AND point3 IS NULL;
CREATE UNIQUE INDEX sample_idx2 ON sample (point1, point2) WHERE point3 IS NULL;
CREATE UNIQUE INDEX sample_idx3 ON sample (point1, point2, point3);

これがPostgreSQL 15からは、NULLS NOT DISTINCTで一つのUNIQUE INDEXで弾けるようになります。

CREATE UNIQUE INDEX sample_idx ON sample (point1, point2, point3) NULLS NOT DISTINCT;

Discussion