Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PostgreSQL unique value acreoss multiple columns

I have PostgreSQL table

id    ColA    ColB
------------------
1     'a'     'b'
2     'c'     'd'

I want to make values in ColA and ColB to be unique across both columns i.e. any of these inserts would be forbidden:

INSERT INTO table (ColA,ColB) values('a','e');
INSERT INTO table (ColA,ColB) values('z','a');
INSERT INTO table (ColA,ColB) values('d','g');

and any of these inserts would be allowed:

INSERT INTO table (ColA,ColB) values('z','e');
INSERT INTO table (ColA,ColB) values('l','k');

So

CONSTRAINT unique_name UNIQUE (ColA,ColB)

is not suited, because it will allow any of previous 4 inserts.

like image 476
Anton Avatar asked Jun 11 '15 09:06

Anton


People also ask

How do I get unique values from multiple columns?

Select Text option from the Formula Type drop down list; Then choose Extract cells with unique values (include the first duplicate) from the Choose a fromula list box; In the right Arguments input section, select a list of cells that you want to extract unique values.

Does distinct work on multiple columns?

Yes, DISTINCT works on all combinations of column values for all columns in the SELECT clause.

How can I get distinct values from multiple columns in SQL?

In SQL multiple fields may also be added with DISTINCT clause. DISTINCT will eliminate those rows where all the selected fields are identical.

How do I make two columns unique in PostgreSQL?

The syntax for creating a unique constraint using an ALTER TABLE statement in PostgreSQL is: ALTER TABLE table_name ADD CONSTRAINT constraint_name UNIQUE (column1, column2, ... column_n); table_name.


1 Answers

Sadly, this cannot be solved easily with simple unique contraints / indexes (if it can be solved with them at all).

What you need, is an exclusion constraint: the ability to exclude some rows, based on something like collision. Unique constraints are just specific exclusion constraints (they are based on equality collisions).

So, in theory, you just need to exclude every row1, where there is already a row2, for which this expression is true: ARRAY[row1.cola, row1.colb] && ARRAY[row2.cola, row2.colb]

This index could do the job (currently only gist indexes support exclusion constraints):

ALTER TABLE table_name
  ADD CONSTRAINT table_name_exclusion
  EXCLUDE USING gist ((ARRAY[cola, colb]) WITH &&);

But unfortunately, there is no default operator class for arrays (which uses gist). There is an intarray module, which provides one for only integer arrays, but nothing for text arrays.

If you really want to work this out, you can always abuse the range types (f.ex. I used the adjacent -|- operator, which handles all the cases, which cannot be handled with unique) ...

-- there is no built-in type for text ranges neither,
-- but it can can be created fairly easily:
CREATE TYPE textrange AS RANGE (
  SUBTYPE = text
);

ALTER TABLE table_name
  ADD CONSTRAINT table_name_exclusion
  EXCLUDE USING gist ((textrange(least(cola, colb), greatest(cola, colb))) WITH -|-);

-- the exclusion constraint above does not handle all situations:

ALTER TABLE table_name
  ADD CONSTRAINT table_name_check
  CHECK (cola is distinct from colb); -- without this, empty ranges could be created,
                                      -- which are not adjacent to any other range

CREATE UNIQUE INDEX table_name_unique
  ON table_name ((ARRAY[least(cola, colb), greatest(cola, colb)]));
     -- without this, duplicated rows could be created,
     -- because ranges are not adjacent to themselves

... but I'm afraid, your original problem could be solved much easier with a little database refactoring; which brings us to the question: what problem, do you want to solve with this?

like image 189
pozs Avatar answered Sep 30 '22 13:09

pozs