2012-09-19 97 views
2

這不是兩列的典型約束。MySQL - 兩列的組合約束

這裏是一個表的外鍵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對 - 對我來說,pair(ref1,ref2)或(1,2)與(2,1)相同,並且應該被唯一的鍵約束禁止。有沒有辦法在MySQL中做到這一點?

我確信它已經被回答了,但是在搜索時我總是碰到兩列上的典型唯一約束。

回答

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))文字作品 - 在這裏假的代碼,使用最多更多鈔票的價值和插入或更新前觸發內計算出它作爲 CONCAT(min(ref1_id, ref2_id), ' ', max(ref1_id, ref2_id)) - 這種方式,整個由於違反唯一約束條件,行將被拒絕。

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更糟:剛發現黑客中止在觸發查詢(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 ; 
+0

+1對於這些想法,不幸的是,強制的命令不適用於我的情況,我想盡可能地避免使用觸發器,如果​​可以的話,也許我必須作爲最後的手段來做到這一點。 – Resh32

+0

你有這樣的觸發器應該拋出一個錯誤的例子嗎? – Resh32

+0

如果我正確理解邏輯,這些觸發器將默默忽略違反約束的行,而不是拒絕執行插入/更新? – wildplasser

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的回答一樣。

順便說一句:在該模型中,對約束(AAA,BBB)是多餘的,因爲該條件已經由索引覆蓋。 (我想讓它們交換:{至少,最大}上的約束和裸列上的索引,但postgres似乎還沒有允許這樣做)

+0

'SERIAL'是postgresql(擴展)的典型代碼,在mysql中你寫成'int auto_increment' – xception

+1

代理鍵是完全不相關的,甚至是多餘的,應該省略。我剛剛發佈了用於測試的代碼,它與OP的*意圖*非常相似。 – wildplasser

+0

+1因爲這似乎是postgres的一個好主意,但在MySQL上,我有錯誤:'錯誤代碼:1064.你的SQL語法有錯誤;請在第1行查看與您的MySQL服務器版本相對應的手冊,以便在'aaa,bbb),GREATEST(aaa,bbb))'附近使用正確的語法' – Resh32