这不是两列的典型约束。
这是一个包含外键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中做到这一点?
我确信它已经被回答了,但是在搜索时我总是在两列上找到典型的唯一约束。
答案 0 :(得分:1)
您需要在INSERT
/ UPDATE
个查询中使用规范的数据顺序(例如,ref1_id始终小于ref2_id)或使用ON BEFORE INSERT
/ UPDATE
触发器自定检查重复项
此任务无法仅使用约束来解决。
编辑:
没有办法用触发器中止INSERT
或UPDATE
语句,所以整个解决方案更糟糕: - )
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的回答一样。
答案 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))
- 这样,整行将因唯一约束违规而被拒绝。