2016-07-20 5 views
5

Прежде всего, я не очень разбираюсь в дизайне базы данных. У меня есть таблица хэшей и идентификаторов. Когда добавляется группа новых хешей, каждая строка в группе получает одинаковый идентификатор. Если какой-либо хэш в новой группе уже существует в базе данных, все хэш в новой группе и существующая группа (ы) получить новый, общий идентификатор (фактически объединение идентификаторов, когда хэш повторяются):Игнорировать каскад при обновлении внешнего ключа?

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 и 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, поэтому, если существует повторяющаяся строка, любые дублирующие вставки игнорируются. Тем не менее, я уверен, что реальная проблема здесь - это мой дизайн базы данных, поэтому я открыт для любых предложений. Мое текущее решение заключается в том, чтобы не применять уникальность в дочерних таблицах, что приводит к множеству избыточных строк.

Edit:

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 
+0

Пожалуйста, покажите определение таблиц. – Renzo

+0

Я бы выбрал каскад обновления и вручную выполнил эту часть. Для меня эта концепция всегда играет с огнем. – Drew

+0

@Renzo Обновлен с таблицей хешей пример дочерней таблицы, в данном случае таблицы, содержащей электронные письма. – Charlie

ответ

1

Решение, которое мы прибыли в чате chat:

/* 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; 
3

Я думаю, что будет хорошо, чтобы создать таблицу hash_group хранить идентификатор хэш-группы:

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 ; 

И создать функцию для получения фактической идентификатор группы:

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', '[email protected]'), 
(2, 'g1', '[email protected]'), 
(3, 'g1', '[email protected]'); 

Обновление 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; 
+0

Я не думаю, что этот метод будет работать. Например, если я добавил в хэши значения «hash11» и «hash3» в одно и то же время, учитывая то, что уже существует, каков будет результат? Я могу лучше объяснить вам вопрос в чате http://chat.stackoverflow.com/rooms/117832/on-update-ignore-cascade – Charlie

+0

Вы можете использовать 'UPDATE .. ON DUPLICATE KEY UPDATE repeat_count = repeat_count + 1' и создайте триггер для обновления хэшей для этой цели. Вы можете обновить 'hash_group.group_name' из триггера. – mnv

+0

Можете ли вы показать пример этого триггера? Мне сложно документировать рабочий процесс. – Charlie

2

Возможно, я ошибаюсь, но, думаю, вы неправильно назвали поле id в hashes.

Я думаю, вы должны переименовать id поля в hashes к чему-то вроде group_id, то есть AUTO_INCREMENT поля, называемого id, который также должен быть первичным в hashes, что id в emails относится к этой области вместо. Когда вы хотите обновить и связать все хеши вместе, вы обновите поле group_id вместо id, а id останется уникальным через стол.

Таким образом, вы можете избежать проблемы с каскадом, также вы всегда будете знать исходный хеш, на который ссылалось электронное письмо. Конечно, если вы хотите получить все хеши, связанные с электронной почтой (старым и новым), вы должны выслушать и добавить запрос, но я думаю, что он решает все ваши проблемы.

Edit:
вы можете использовать триггер, чтобы сделать это

Триггер идет как этот

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 ; 

и вы должны удалить внешнего ключа отношения тоже.

+0

Тогда все равно будут повторяться повторяющиеся письма, относящиеся к тому же 'id'. Конечно, 'group_id' делает его« уникальным », но это то, что я делаю в своей текущей реализации. Я просто понял, что забыл уникальный ключ в таблице электронных писем. Как вы можете видеть, то, что я сейчас делаю, более или менее точно означает то, что вы предлагаете. – Charlie

+0

@Charlie Я знаю, что триггер решает вашу проблему, сохраняя при этом вашу первоначальную структуру. Я обновляю ответ, чтобы записать триггер. –

+0

Я не вижу, как эта реализация отличается от той, которую я сейчас имею. Это просто похоже на другой способ сделать то же самое, не так ли? – Charlie

1

Добавление дополнительного целочисленного столбца в каждую из дочерних таблиц позволит избежать этой проблемы в целом, используя ее в качестве первичного ключа. Ключ никогда не изменяется, потому что это не ссылка на что-либо еще.

Использование составных клавиш в качестве первичных ключей обычно является тем, чего вы хотите избежать. И учитывая, что эта комбинация клавиш не всегда уникальна, я бы определенно сказал, что вам нужна специальная первичная клавиша во всех ваших дочерних таблицах с этой проблемой.

Вы можете даже автоматически увеличивать его, чтобы вы не вручную назначали его каждый раз. Например ..

Create Table exampleTable 
( 
    trueID int NOT NULL AUTO_INCREMENT, 
    col1 int NOT NULL, 
    col2 varChar(50) 
    PRIMARY KEY(trueID) 
) 

Затем, когда два из строк в дочерней таблице устанавливается одинаковыми значениями (по любой причине), первичный ключ остается уникальным, предотвращая любые конфликты в базе данных, которые могут возникнуть.

Смежные вопросы