Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Postgres unique multi-column index for join table

I have a many-to-many join table in Postgres that I would like to index to A) increase performance (obviously) and B) enforce uniqueness. For example:

a_id | b_id
1    | 2     <- okay
1    | 3     <- okay
2    | 3     <- okay
1    | 3     <- not okay (same as row 2)

Is it possible to have a single index on two columns that enforces uniqueness in the values? What type of index should I use?

like image 606
bloudermilk Avatar asked May 06 '12 06:05

bloudermilk


People also ask

Can we create index on multiple columns in PostgreSQL?

You can create an index on more than one column of a table. This index is called a multicolumn index, a composite index, a combined index, or a concatenated index. A multicolumn index can have maximum 32 columns of a table. The limit can be changed by modifying the pg_config_manual.

How does multi column index work in PostgreSQL?

A multicolumn GiST index can be used with query conditions that involve any subset of the index's columns. Conditions on additional columns restrict the entries returned by the index, but the condition on the first column is the most important one for determining how much of the index needs to be scanned.

What is unique index with multiple columns?

A unique index ensures the index key columns do not contain any duplicate values. A unique index may consist of one or many columns. If a unique index has one column, the values in this column will be unique. In case the unique index has multiple columns, the combination of values in these columns is unique.


1 Answers

As Primary Key

Do this if that unique is primary key:

create table tbl(
   a_id int not null,
   b_id int not null,
   constraint tbl_pkey primary key(a_id,b_id)
);

Not Primary Key

Do this if that unique is non-primary key:

create table tbl(

   -- other primary key here, e.g.:
   -- id serial primary key,

   a_id int not null,
   b_id int not null,
   constraint tbl_unique unique(a_id,b_id)
);

Existing Table

If you have existing table, do this instead:

alter table tbl
      add constraint tbl_unique unique(a_id, b_id)

That alter table display this message:

NOTICE:  ALTER TABLE / ADD UNIQUE will create implicit index "tbl_unique" for table "tbl"


Query returned successfully with no result in 22 ms.

Drop

If you wanted to drop that constraint(you might want to make unique a combination of 3 fields):

ALTER TABLE tbl DROP CONSTRAINT tbl_unique;

Index & Constraint & Nulls

Regarding index, from Postgres doc:

PostgreSQL automatically creates a unique index when a unique constraint or primary key is defined for a table

Source: http://www.postgresql.org/docs/9.1/static/indexes-unique.html


If uniqueness depends on some rules, you shall use CREATE UNIQUE INDEX, for example:

Given this:

CREATE TABLE tbl
(
  a_id integer NOT NULL,
  b_id integer NULL  
);

alter table tbl
    add constraint tbl_unique unique(a_id, b_id);

That unique can catch these duplicates, this will be rejected by database:

insert into tbl values
(1,1),
(1,1);

Yet that UNIQUE CONSTRAINT cannot catch duplicate nulls. Nulls serves as unknown, they serves as wildcard, that's why it's allowed to have multiple nulls in unique constraint. This will be accepted by database:

insert into tbl values
(1,1),
(1,null), -- think of this null as wildcard, some real value can be assigned later.
(1,null); -- and so is this. that's why both of these nulls are allowed

Think of UNIQUE CONSTRAINT that it allows deferred uniqueness, hence the acceptance of null values above.

If you want only one wildcard(null b_id) per a_id, aside from the unique constraint, you need to add a UNIQUE INDEX. UNIQUE CONSTRAINT can't have an expression on them. INDEX and UNIQUE INDEX can. This will be your complete DDL for rejecting multiple null;

This will be your complete DDL:

CREATE TABLE tbl
(
  a_id integer NOT NULL,
  b_id integer NULL  
);
alter table tbl
    add constraint tbl_unique unique(a_id, b_id);

create unique index tbl_unique_a_id on tbl(a_id) where b_id is null;      

This will be rejected by your database now:

insert into tbl values
(1,1),
(1,null),
(1,null);

This will be allowed:

insert into tbl values
(1,1),
(1,null);

Related to http://www.ienablemuch.com/2010/12/postgresql-said-sql-server2008-said-non.html

like image 179
Michael Buen Avatar answered Oct 19 '22 23:10

Michael Buen