简体   繁体   中英

postgres many to one unique constraint

I'm curious if there is a way to write a unique constraint to support the following situation.

Suppose I have table table1 with facts about a user, with four columns:

  • user_id : unique id for user
  • source : where the detail came from
  • d1 : dimension 1 of the fact
  • d2 : dimension 2 of the fact

The following is an example of data in this table:

| row_id | user_id | source | d1     | d2      |
|--------|---------|--------|--------|---------|
| 1      | aaa111  | foo    | bar    | 123     |
| 2      | aaa111  | foo    | baz    | 'horse' |
| 3      | aaa111  | scrog  | bar    | 123     |
| 4      | bbb222  | foo    | goober | 456     |

Currently, a unique constraint exists on source + d1 + d2 . This is good, because it allows the same user to have duplicates of (d1,d2) , as long as they have a different source . Rows #1 and #3 demonstrate this for user aaa111 .

However, this constraint does not prevent the following row from getting added...

| row_id | user_id | source | d1     | d2      |
|--------|---------|--------|--------|---------|
| 1      | aaa111  | foo    | bar    | 123     |
| 2      | aaa111  | foo    | baz    | 'horse' |
| 3      | aaa111  | scrog  | bar    | 123     |
| 4      | bbb222  | foo    | goober | 456     |
| 5      | bbb222  | turnip | baz    | 'horse' |  <---- allowed new row

...because source is different for rows #2 and #5.

I would like to add a unique constraint where the combination of (d1,d2) may only exist for a single user_id .

Said another way, a single user can have as many unique (source, d1, d2) combinations as needed, but cannot share (d1,d2) with another user_id .

Is this data model fundamentally flawed to support this constraint? or is there a unique constraint that might help enforce this? Thanks in advance for any suggestions.

It's a conditional-constraint, you can use a trigger BEFORE INSERT OR UPDATE that raise exception when violate the constraint:

CREATE OR REPLACE FUNCTION check_user_combination() RETURNS trigger AS
$$
DECLARE
  vCheckUser INTEGER;
BEGIN
  SELECT INTO vCheckUser user_id
  FROM table1 
  WHERE d1 = NEW.d1 
    AND d2 = NEW.d2 
    AND user_id <> NEW.user_id;

  IF vCheckUser IS NOT NULL THEN
    RAISE EXCEPTION 'User % have already d1=% and d2=%',vCheckUser,NEW.d1, NEW.d2;
  END IF;
  RETURN NEW;
END;
$$
  language 'plpgsql';

CREATE TRIGGER tr_check_combination BEFORE INSERT OR UPDATE ON table1 FOR EACH ROW EXECUTE PROCEDURE check_user_combination();

This prevent insert or update additional user for the same d1 and d2.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM