MySQL - 两列的组合约束

时间:2012-09-19 09:53:34

标签: mysql sql unique

这不是两列的典型约束。

这是一个包含外键ref1,ref2:

的表
connection_id | ref1_id  | ref2_id
1             |     1    |    2

我想允许:

connection_id | ref1_id  | ref2_id
1             |     1    |    2
2             |     1    |    3

但不允许这两个:

(typical unique key on ref1,ref2 - this I know how to do)
connection_id | ref1_id  | ref2_id
1             |     1    |    2
2             |     1    |    2

但也!!:

(this is the problem)
connection_id | ref1_id  | ref2_id
1             |     1    |    2
2             |     2    |    1

因为我只想要一个ref1-ref2对 - 对我来说,对(ref1,ref2)或(1,2)与(2,1)相同,并且应该被唯一的键约束禁止。有没有办法在MySQL中做到这一点?

我确信它已经被回答了,但是在搜索时我总是在两列上找到典型的唯一约束。

3 个答案:

答案 0 :(得分:1)

您需要在INSERT / UPDATE个查询中使用规范的数据顺序(例如,ref1_id始终小于ref2_id)或使用ON BEFORE INSERT / UPDATE触发器自定检查重复项

此任务无法仅使用约束来解决。

编辑: 没有办法用触发器中止INSERTUPDATE语句,所以整个解决方案更糟糕: - )

DELIMITER ###

CREATE TRIGGER `after_up` 
  AFTER UPDATE ON `my_table`
FOR EACH ROW 
BEGIN
  DECLARE collision INT DEFAULT 0;
  SELECT 1  
    INTO collision
    FROM my_table 
    WHERE ref1_id = NEW.ref2_id AND ref2_id = NEW.ref1_id;
  IF collision
  THEN -- reverting update
    UPDATE my_table SET ref2_id = OLD.ref2_id, ref1_id = OLD.ref1_id WHERE connection_id = OLD.connection_id;
  END IF;
END

###

CREATE TRIGGER `after_in` 
  AFTER INSERT ON `my_table`
FOR EACH ROW 
BEGIN
  DECLARE collision INT DEFAULT 0;
  SELECT 1  
    INTO collision
    FROM my_table 
    WHERE ref1_id = NEW.ref2_id AND ref2_id = NEW.ref1_id;
  IF collision
  THEN -- deleting new row
    DELETE FROM my_table WHERE connection_id = NEW.connection_id;
  END IF;
END

###

delimiter ;

编辑2:刚刚发现hack在触发器(DROP TABLE nonexistent_table_name

中中止查询
DELIMITER ###

CREATE TRIGGER `before_up` 
  BEFORE UPDATE ON `my_table`
FOR EACH ROW 
BEGIN
  DECLARE collision INT DEFAULT 0;
  SELECT 1  
    INTO collision
    FROM my_table 
    WHERE ref1_id = NEW.ref2_id AND ref2_id = NEW.ref1_id;
  IF collision
  THEN -- throwing error
     DROP TABLE __error_duplicate_detected;        
  END IF;
END

###

CREATE TRIGGER `before_in` 
  BEFORE INSERT ON `my_table`
FOR EACH ROW 
BEGIN
  DECLARE collision INT DEFAULT 0;
  SELECT 1  
    INTO collision
    FROM my_table 
    WHERE ref1_id = NEW.ref2_id AND ref2_id = NEW.ref1_id;
  IF collision
  THEN -- throwing error
     DROP TABLE __error_duplicate_detected;        
  END IF;
END

###

delimiter ;

答案 1 :(得分:1)

DROP TABLE pair CASCADE;
CREATE TABLE pair
      ( pair_id SERIAL NOT NULL PRIMARY KEY
      , aaa INTEGER NOT NULL
      , bbb INTEGER NOT NULL
      , CONSTRAINT aaa_bbb UNIQUE (aaa, bbb)
    );

CREATE UNIQUE INDEX aaaXXXbbb ON pair ( LEAST(aaa, bbb), GREATEST(aaa, bbb) )
        ;
INSERT INTO pair(aaa,bbb) VALUES(1,1), (1,2),(2,2);

INSERT INTO pair(aaa,bbb) VALUES(2,1);

SELECT * FROM pair;

结果:

INSERT 0 3
ERROR:  duplicate key value violates unique constraint "aaaxxxbbb"
DETAIL:  Key ((LEAST(aaa, bbb)), (GREATEST(aaa, bbb)))=(1, 2) already exists.
 pair_id | aaa | bbb 
---------+-----+-----
       1 |   1 |   1
       2 |   1 |   2
       3 |   2 |   2
(3 rows)

我不知道mysql是否允许并强制执行表达式的约束或索引。 Postgres确实允许表达式的索引,但不幸的是没有表达式的约束。如果索引不可能,那么明显的打破平局约束当然是aaa >= bbb,就像@vearutob的回答一样。

BTW:在这个模型中,对(aaa,bbb)的约束是多余的,因为条件已经被索引所覆盖。 (我想交换它们:{least,great}上的约束和裸列上的索引,但postgres似乎还不允许这样做)

答案 2 :(得分:1)

变量1:考虑ref1_id和ref2_id的数据类型在整数数据类型的限制范围内,并且存在一个整数数据类型,它可以在mysql中适合sizeof ref1_id + sizeof ref2_id

将新的计算列添加到您的表中,其类型可以适合它并计算为 在插入或更新之前触发器内的min(ref1_id, ref2_id) << (size_in_bits_of_the_biggest_of_the_types_of_ref1_id_and_ref2_id) + max(ref1_id, ref2_id) - 这样,整行将因唯一约束违规而被拒绝。

变式2:如果变量1不适用,则使用此项,因为它会更慢。

将新的计算列添加到您的表中作为varchar足够长以适合concat(max(ref1_id), ' ', max(ref2_id))的文本表示 - 假代码,在此处使用max posible值并将其计算为 在插入或更新之前触发器内的CONCAT(min(ref1_id, ref2_id), ' ', max(ref1_id, ref2_id)) - 这样,整行将因唯一约束违规而被拒绝。