[英]Ignore cascade on foreign key update?
作為前言,我對數據庫設計不是很有經驗。 我有一張哈希和ids表。 添加一組新哈希時,組中的每一行都會獲得相同的ID。 如果數據庫中已存在新組中的任何哈希,則新組和現有組中的所有哈希都會獲得一個新的共享ID(在重復哈希時有效地合並ID):
INSERT INTO hashes
(id, hash)
VALUES
($new_id, ...), ($new_id, ...)
ON DUPLICATE KEY UPDATE
repeat_count = repeat_count + 1;
INSERT INTO hashes_lookup SELECT DISTINCT id FROM hashes WHERE hash IN (...);
UPDATE hashes JOIN hashes_lookup USING (id) SET id = '$new_id';
TRUNCATE TABLE hashes_lookup;
其他表引用了這些id,因此如果id發生更改,則外鍵約束會負責更新表中的id。 但是,這里的問題是我無法在任何子表中強制實現唯一性。 如果我這樣做,我的查詢失敗:
表'...'的外鍵約束,記錄'...'將導致表'...'中的重復條目
考慮到以下測試用例,其中id
和value
是復合唯一鍵,這個錯誤是有意義的:
id | value
---+-------
a | 1
b | 2
c | 1
然后a
變為c
:
id | value
---+-------
c | 1
b | 2
c | 1
但是, c,1
已經存在。
如果存在ON UPDATE IGNORE CASCADE
選項,那將是理想的,因此如果存在重復行,則忽略任何重復插入。 但是,我很確定這里真正的問題是我的數據庫設計,所以我對所有建議都持開放態度。 我目前的解決方案是不強制跨子表的唯一性,這會導致大量冗余行。
編輯:
CREATE TABLE `hashes` (
`hash` char(64) NOT NULL,
`id` varchar(128) NOT NULL,
`repeat_count` int(11) NOT NULL DEFAULT '0',
`insert_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `hash` (`hash`) USING BTREE,
KEY `id` (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=latin1
CREATE TABLE `emails` (
`id` varchar(128) NOT NULL,
`group_id` char(5) NOT NULL,
`email` varchar(500) NOT NULL,
KEY `index` (`id`) USING BTREE,
UNIQUE KEY `id` (`id`,`group_id`,`email`(255)) USING BTREE,
CONSTRAINT `emails_ibfk_1` FOREIGN KEY (`id`) REFERENCES `hashes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1
我認為創建表hash_group
來存儲哈希組的id會很好:
CREATE TABLE `hash_group` (
`id` BIGINT AUTO_INCREMENT NOT NULL,
`group_name` varchar(128) NOT NULL,
`insert_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `group_name` (`group_name`) USING BTREE,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
並改變現有表的結構:
CREATE TABLE `hashes` (
`hash` char(64) NOT NULL,
`hash_group_id` BIGINT NOT NULL,
`repeat_count` int(11) NOT NULL DEFAULT '0',
`insert_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `hash` (`hash`) USING BTREE,
KEY `hashes_hash_group_id_index` (`hash_group_id`) USING BTREE,
CONSTRAINT `hashes_hash_group_id_fk` FOREIGN KEY (`hash_group_id`) REFERENCES `hash_group` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `emails` (
`hash_group_id` BIGINT NOT NULL,
`group_id` char(5) NOT NULL,
`email` varchar(500) NOT NULL,
KEY `emails_hash_group_id_index` (`hash_group_id`) USING BTREE,
UNIQUE KEY `emails_unique` (`hash_group_id`,`group_id`,`email`(255)) USING BTREE,
CONSTRAINT `emails_ibfk_1` FOREIGN KEY (`hash_group_id`) REFERENCES `hash_group` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
如果需要,還可以創建更新哈希組的觸發器:
DELIMITER $$
CREATE TRIGGER `update_hash_group_name` AFTER UPDATE ON `hashes`
FOR EACH ROW
BEGIN
UPDATE `hash_group`
SET `group_name` = md5(now()) -- replace to you hash formula
WHERE id = NEW.hash_group_id;
END;$$
DELIMITER ;
並創建獲取實際組ID的函數:
DROP FUNCTION IF EXISTS get_hash_group;
DELIMITER $$
CREATE FUNCTION get_hash_group(id INT) RETURNS INT
BEGIN
IF (id IS NULL) THEN
INSERT INTO `hash_group` (`group_name`)
VALUES (md5(now())); -- replace to you hash
RETURN LAST_INSERT_ID();
END IF;
RETURN id;
END;$$
DELIMITER ;
場景:
初始填寫:
INSERT INTO `hash_group` (id, group_name) VALUES
(1, 'test1'),
(2, 'test2'),
(3, 'test3');
INSERT INTO `hashes` (hash, hash_group_id) VALUES
('hash11', 1),
('hash12', 1),
('hash13', 1),
('hash2', 2),
('hash3', 3);
INSERT INTO `emails` (hash_group_id, group_id, email)
VALUES
(1, 'g1', 'example1@'),
(2, 'g1', 'example2@'),
(3, 'g1', 'example2@');
更新hash_group
場景:
START TRANSACTION;
-- Get @min_group_id - minimum group id (we will leave this id and delete other)
SELECT MIN(hash_group_id) INTO @min_group_id
FROM hashes
WHERE hash IN ('hash11', 'hash12', 'hash2', 'hash15');
-- Replace other group ids in email table to @min_group_id
UPDATE `emails`
SET `hash_group_id` = @min_group_id
WHERE `hash_group_id` IN (
SELECT hash_group_id
FROM hashes
WHERE @min_group_id IS NOT NULL
AND hash IN ('hash11', 'hash12', 'hash2', 'hash15')
-- Update only if we are gluy several hash_groups
AND `hash_group_id` > @min_group_id
);
-- Delete other hash_groups and leave only group with @min_group_id
DELETE FROM `hash_group` WHERE `id` IN (
SELECT hash_group_id
FROM hashes
WHERE @min_group_id IS NOT NULL
AND hash IN ('hash11', 'hash12', 'hash2', 'hash15')
-- Delete only if we are gluy several hash_groups
AND `hash_group_id` > @min_group_id
);
-- @group_id = existing hash_group.id or create new if @min_group_id is null (all inserted hashes are new)
SELECT get_hash_group(@min_group_id) INTO @group_id;
-- Now we can insert new hashes.
INSERT INTO `hashes` (hash, hash_group_id) VALUES
('hash11', @group_id),
('hash12', @group_id),
('hash2', @group_id),
('hash15', @group_id)
ON DUPLICATE KEY
UPDATE repeat_count = repeat_count + 1;
COMMIT;
我可能錯了,但我認為你在hashes
錯誤地命名了id
字段。
我認為你應該將hashes
的id
字段重命名為group_id
,然后有一個名為id
的AUTO_INCREMENT
字段,該字段在hashes
中也應該是主要的,而emails
中的id
引用此字段。 如果要更新所有哈希並將其關聯在一起,則更新group_id
字段而不是id
,並且id
在整個表中保持唯一。
這樣您就可以避免級聯問題,也可以始終知道電子郵件所指的原始哈希值。 當然,如果你想要獲取與電子郵件(舊的和新的)相關的所有哈希,你必須執行和額外的查詢,但我認為它解決了所有問題。
編輯:
你可以使用觸發器來做到這一點
觸發器就像這樣
DELIMITER $$
CREATE TRIGGER `update_hash_id` AFTER UPDATE ON `hashes`
FOR EACH ROW
BEGIN
UPDATE `emails` SET `id` = NEW.id WHERE `id` = OLD.id;
END;$$
DELIMITER ;
你也必須刪除外鍵關系。
向每個子表添加一個額外的整數列可以通過將其用作主鍵來完全避免此問題。 密鑰永遠不會改變,因為它不是對其他任何東西的引用。
使用復合鍵作為主鍵通常是您要避免的。 考慮到這個組合鍵並不總是唯一的,我肯定會說你需要在所有子表中都有一個專用的主鍵來解決這個問題。
您甚至可以自動遞增它,這樣您就不會每次都手動分配它。 例如..
Create Table exampleTable
(
trueID int NOT NULL AUTO_INCREMENT,
col1 int NOT NULL,
col2 varChar(50)
PRIMARY KEY(trueID)
)
然后,當子表中的兩個行設置為相同的值(無論出於何種原因)時,主鍵保持唯一,從而防止可能出現的數據庫中的任何沖突。
解決方案,我們已經進入聊天聊天 :
/* Tables */
CREATE TABLE `emails` (
`group_id` bigint(20) NOT NULL,
`email` varchar(500) NOT NULL,
UNIQUE KEY `group_id` (`group_id`,`email`) USING BTREE,
CONSTRAINT `emails_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `entities` (`group_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1
CREATE TABLE `hashes` (
`group_id` bigint(20) NOT NULL,
`hash` varchar(128) NOT NULL,
`repeat_count` int(11) NOT NULL DEFAULT '0',
UNIQUE KEY `hash` (`hash`),
KEY `group_id` (`group_id`),
CONSTRAINT `hashes_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `entities` (`group_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1
CREATE TABLE `entities` (
`group_id` bigint(20) NOT NULL,
`entity_id` bigint(20) NOT NULL,
PRIMARY KEY (`group_id`),
KEY `entity_id` (`entity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
CREATE TABLE `entity_lookup` (
`entity_id` bigint(20) NOT NULL,
PRIMARY KEY (`entity_id`) USING HASH
) ENGINE=MyISAM DEFAULT CHARSET=latin1
/* Inserting */
START TRANSACTION;
/* Determine next group ID */
SET @next_group_id = (SELECT MAX(group_id) + 1 FROM entities);
/* Determine next entity ID */
SET @next_entity_id = (SELECT MAX(entity_id) + 1 FROM entities);
/* Merge any entity ids */
INSERT IGNORE INTO entity_lookup SELECT entity_id FROM entities JOIN hashes USING(group_id) WHERE HASH IN(...);
UPDATE entities JOIN entity_lookup USING(entity_id) SET entity_id = @next_entity_id;
TRUNCATE TABLE entity_lookup;
/* Add the new group ID to entity_id */
INSERT INTO entities(group_id, entity_id) VALUES(@next_group_id, @next_entity_id);
/* Add new values into hashes */
INSERT INTO hashes (group_id, HASH) VALUES
(@next_group_id, ...)
ON DUPLICATE KEY UPDATE
repeat_count = repeat_count + 1;
/* Add other new values */
INSERT IGNORE INTO emails (group_id, email) VALUES
(@next_group_id, "email1");
COMMIT;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.