Skip to main content
3 of 6
add uniqueness & arrays & search in arrays
Erwin Brandstetter
  • 182.1k
  • 28
  • 457
  • 620

Your table definition looks reasonable all over now. With all columns NOT NULL the UNIQUE constraint will work as expected - except for typos and minor differences in spelling, which may be rather common I am afraid. Consider @a_horse's comment.

###Uniqueness and arrays Arrays would have to be sorted consistently to make sense in any unique arrangement relying on the = operator because '{1,2}' <> '{2,1}'. I suggest look-up tables for genre, tag and origin with serial PK and unique entries, which allow fuzzy search for array elements. Then:

Either way, working with arrays directly or with a normalized schema and a materialized view, searching can be very efficient with the right index and operators:

###Alternative with functional unique index

The other option would be functional unique index (similar to what @Dave commented). But I would use a uuid data type to optimize index size and performance.

The cast from array to text is not IMMUTABLE (due to its generic implementation):

Hence you need a little helper function to declare it immutable:

CREATE OR REPLACE FUNCTION f_movie_uuid(_title text
                                      , _runtime int2
                                      , _released_in int2
                                      , _genres text[]
                                      , _tags text[]
                                      , _origin text[])
  RETURNS uuid LANGUAGE sql IMMUTABLE AS  -- faking IMMUTABLE
'SELECT md5(_title || _runtime::text || _released_in::text
         || _genres::text || _tags::text || _origin::text)::uuid';

Use it for the index definition:

CREATE UNIQUE INDEX movies_uni_idx
ON movies (f_movie_uuid(title,runtime,released_in,genres,tags,origin));

SQL Fiddle.

More Details:

You might use the generated UUID as PK, but I would still rather use the serial column with it's 4 bytes, which is simple and cheap for FK references and all other purposes. The UUID would be a great option for distributed systems that need to generate a PK independently. Or for very huge tables, but there aren't nearly enough movies in our solar system for that.

###Pros and Cons The constraint is implemented with a UNIQUE index on the involved columns. Put relevant columns in the constraint definition first and you have useful index for other purposes as collateral benefit.

There are other small benefits:

The functional unique index is (potentially much) smaller in size, which can make it substantially faster. If your columns are not too big, the difference won't be much. There is also the small overhead cost for the calculation.

Concatenating all columns can introduce false positives ('foo ' || 'bar' = 'foob ' || 'ar', but that seems very unlikely for this case. Typos are so much more likely that you can safely ignore it here.

Aside:
If you are using Postgres 9.4 or later consider jsonb instead of json.

Erwin Brandstetter
  • 182.1k
  • 28
  • 457
  • 620