Nicolas
Nicolas

Reputation: 1870

Mysql : Association table

Using mySQL, I have these tables:

Person
----------
id (PK)
name
...


Person_Association
----------
id_Person1  (FK Person.id)
id_Person2  (FK Person.id)

I want each Person_Association to be unique: if (#1, #2) exists, then neither (#1, #2) nor (#2, #1) can be inserted.

To do that I've added a field and a trigger to Person_Association, like this:

Person_Association
----------
id_Person1  (FK Person.id)
id_Person2  (FK Person.id)
unique_id   (PK)


CREATE TRIGGER `Person_Association_BINS` BEFORE INSERT ON `Person_Association` FOR EACH ROW
BEGIN
  IF (new.id_Person1 < new.id_Person2) THEN
    SET new.unique_id = CONCAT(new.id_Person1, '-', new.id_Person2);
  ELSE
    SET new.unique_id = CONCAT(new.id_Person2, '-', new.id_Person1);
  END IF;
END

It works, but is there a better way to do that?

Upvotes: 0

Views: 3318

Answers (1)

fancyPants
fancyPants

Reputation: 51888

You better throw away the trigger and have an unique index on (id_Person1, id_Person2).
Actually you can also throw away the unnecessary unique_id and make (id_Person1, id_Person2) the primary key.

Then you insert like this:

INSERT INTO your_table VALUES (LEAST($value1, $value2), GREATEST($value1, $value2));

or

INSERT INTO your_table SELECT LEAST($value1, $value2), GREATEST($value1, $value2);

EDIT: If you really insist on "SQL to assure that (not the application layer)", I'd write it like this (but still have your unique_id column removed and an unique index on (id_Person1, id_Person2)):

DELIMITER $$
CREATE TRIGGER `Person_Association_BINS` BEFORE INSERT ON `Person_Association` FOR EACH ROW
BEGIN
  IF EXISTS(SELECT 1 FROM `Person_Association` WHERE id_Person1 = LEAST(NEW.id_Person1, NEW.id_Person2) AND id_Person2 = GREATEST(NEW.id_Person1, NEW.id_Person2)) THEN
  SIGNAL SQLSTATE '23000' SET MESSAGE_TEXT='dup key error';
  END IF;
END $$
DELIMITER ;

I'd still prefer not to have a trigger on the table (that's from database administrators view, not only do they often cause confusion, but they tend also to get in the way when doing administrators tasks). So, to pursuit my first way of solving it, you can also create a stored procedure to do this for you:

CREATE PROCEDURE insert_p_a(IN p_id1 INT, IN p_id2 INT)
INSERT INTO your_table SELECT LEAST(p_id1, p_id2), GREATEST(p_id1, p_id2);

and in your application layer you just do

CALL insert_p_a($value1, $value2);
  • read more about signals here.
  • here's why I chose 23000 as sqlstate (but in fact it doesn't matter)

Upvotes: 1

Related Questions