Received: from maia.hub.org (unknown [200.46.204.183]) by mail.postgresql.org (Postfix) with ESMTP id D741963EE32 for ; Mon, 29 Jun 2009 21:23:38 -0300 (ADT) Received: from mail.postgresql.org ([200.46.204.86]) by maia.hub.org (mx1.hub.org [200.46.204.183]) (amavisd-maia, port 10024) with ESMTP id 15736-01-8 for ; Mon, 29 Jun 2009 21:23:37 -0300 (ADT) X-Greylist: from auto-whitelisted by SQLgrey-1.7.6 Received: from anumail9.anu.edu.au (anumail9.anu.edu.au [130.56.64.135]) by mail.postgresql.org (Postfix) with ESMTP id 27F4463537E for ; Mon, 29 Jun 2009 21:22:22 -0300 (ADT) Received: from mailout1.anu.edu.au (mailout1.anu.edu.au [130.56.64.232]) by anumail9.anu.edu.au (8.14.3/8.14.3) with ESMTP id n5U0MJOK023795; Tue, 30 Jun 2009 10:22:19 +1000 (EST) Received: from mailout1.anu.edu.au (localhost.localdomain [127.0.0.1]) by localhost (Postfix) with SMTP id D8B8F14A09C0; Tue, 30 Jun 2009 10:22:18 +1000 (EST) Received: from smtphost.anu.edu.au (ds2.anu.edu.au [130.56.64.54]) by mailout1.anu.edu.au (Postfix) with ESMTP id 90F5014A09B5; Tue, 30 Jun 2009 10:22:18 +1000 (EST) Received: from dcsmail.anu.edu.au (dcsmail.anu.edu.au [150.203.164.16]) by smtphost.anu.edu.au (8.14.3/8.14.3) with ESMTP id n5U0MIgQ019516; Tue, 30 Jun 2009 10:22:18 +1000 (EST) Received: from littleblue.anu.edu.au ([150.203.160.52]) by dcsmail.anu.edu.au with esmtp (Exim 4.63) (envelope-from ) id 1MLR6z-0001JF-Bp; Tue, 30 Jun 2009 10:22:18 +1000 Message-ID: <4A495AB4.4000605@cs.anu.edu.au> Date: Tue, 30 Jun 2009 10:22:12 +1000 From: Robert Edwards User-Agent: Thunderbird 2.0.0.18 (X11/20081125) MIME-Version: 1.0 To: Craig Ringer CC: pgsql-sql@postgresql.org References: <4A485431.3080707@cs.anu.edu.au> <1246275150.11346.27.camel@ayaki> In-Reply-To: <1246275150.11346.27.camel@ayaki> Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit X-SA-Exim-Connect-IP: 150.203.160.52 X-SA-Exim-Mail-From: bob@cs.anu.edu.au Subject: Re: uniqueness constraint with NULLs X-SA-Exim-Version: 4.2.1 (built Tue, 09 Jan 2007 17:23:22 +0000) X-SA-Exim-Scanned: Yes (on dcsmail.anu.edu.au) X-PMX-Version: 5.5.6.374947, Antispam-Engine: 2.7.2.376379, Antispam-Data: 2009.6.30.624 internal X-PMX-Outbound: anu.edu.au X-PMX-Info: from-anu X-PerlMx-Spam: Gauge=IIIIIIII, Probability=8%, Report=' BODY_SIZE_2000_2999 0, BODY_SIZE_5000_LESS 0, BODY_SIZE_7000_LESS 0, __BOUNCE_CHALLENGE_SUBJ 0, __CT 0, __CTE 0, __CT_TEXT_PLAIN 0, __HAS_MSGID 0, __MIME_TEXT_ONLY 0, __MIME_VERSION 0, __MOZILLA_MSGID 0, __SANE_MSGID 0, __TO_MALFORMED_2 0, __USER_AGENT 0' X-Virus-Scanned: Maia Mailguard 1.0.1 X-Archive-Number: 200906/123 X-Sequence-Number: 32728 Thanks for all these great ideas! Craig Ringer wrote: > On Mon, 2009-06-29 at 15:42 +1000, Robert Edwards wrote: > >> Can anyone suggest a way that I can impose uniqueness on a and b when >> c is NULL? > > One way is to add an additional partial index on (a,b): > > CREATE INDEX bobtest_ab_unique ON bobtest(a,b) WHERE (c IS NULL); Would this be in addition to a unique constraint on (a, b, c) (for the cases where c is not null)? > > ... however, if you want to do the same sort of thing for all > permutations (a, null, null), (b, null, null), (c, null, null), (a, b, > null), (a, c, null), (b, c, null), (a, b, c)that'll be a LOT of indexes. > In the real app. a and b are not null ints and c is a date. The date indicates if and when a row has expired (there are other columns in the table). I am trying to avoid having separate columns for the "if" and the "when" of the expiry. One alternate would be to use a date way off into the future (such as the famous 9/9/99 case many COBOL programmers used back in the 60's...) and to test on expired < now (). Another option is to use a separate shadow table for the expired rows and to use a trigger function to "move" expired rows to that shadow table. Then need to use UNION etc. when I need to search across both current and expired rows. > In that case you might be better off just using a trigger function like > (untested but should be about right): > > CREATE OR REPLACE FUNCTION bobtest_unique_trigger() RETURNS trigger AS > $$ > declare > conflicting_id integer; > begin > if TG_OP = 'INSERT' or TG_OP = 'UPDATE' then > select into conflicting_id from bobtest > where (NOT new.a IS DISTINCT FROM a) > and (NOT new.b IS DISTINCT FROM b) > and (NOT new.c IS DISTINCT FROM c); > if found then > raise exception 'Unique violation in bobest: inserted row > conflicts with row id=%',conflicting_id; > end if; > end if; > end; > $$ LANGUAGE 'plpgsql'; > > ... which enforces uniqueness considering nulls. I am "guessing" that the "functional index" that Andreas Kretschmer proposed would be a lot "lighter-weight" than a full trigger. This table will get quite a bit of insert activity and some update activity on the "c" (expired) column, so this uniqueness index will get exercised quite a lot. I am concerned that this could cause performance issues with a heavier-weight trigger function (but have no empirical data to back up these concerns...). > >> In the real app., c is a date field and I require it to be NULL for >> some rows. > > Oh. Er, In that case, the partial unique index is your best bet (but 'a' > and 'b' should ne NOT NULL, right). Right - see above. > >> in case I am missing some other solution that >> doesn't involve the use of triggers etc. > > Sometimes a trigger is the right solution. > Yep - I have many of those in other places as well. Cheers, Bob Edwards.