MySQL Database design. Вставка строк в таблицы 1 на 1. - PullRequest
3 голосов
/ 14 июля 2011

Каков наилучший способ вставки строк в таблицы со ссылками 1 на 1 друг друга?

Я имею в виду, что в MySQL 5.5 и таблицах InnoDB у меня есть дизайн базы данных, подобный следующему enter image description here

Проблема возникает, когда мы пытаемся вставить строки в table1 и table2. Поскольку в MySQL нет вставки нескольких таблиц, я не могу вставить строку, потому что внешние ключи не являются полями NOT NULL в обеих таблицах и должны быть вставлены одновременно в обе.

Какой лучший способ решить эту проблему?

Я имею в виду 3 возможных решения, но я хочу знать, есть ли их больше или что является лучшим и почему.

  1. Установите в поле внешнего ключа значение NULLABLE и после вставки одной строки в таблицу вставьте другую, а затем обновите первую.

  2. Так же, как указано выше, но со специальным значением, например -1. Сначала вставьте в одну таблицу с посторонним key = -1, что эквивалентно NULL, но избегайте установки поля как NULLABLE. После этого мы вставляем строку в другую таблицу и обновляем первую вставленную.

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

Спасибо !!

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

Ответы [ 3 ]

7 голосов
/ 14 июля 2011

Я сделаю это ответом, так как чувствую, что это недостаток дизайна.

Во-первых, если две таблицы находятся в истинных отношениях 1:1, почему бы вам просто не иметь одну таблицу?


Во-вторых, если это не истинное 1:1 отношение, а проблема супертипа-подтипа, вам также не нужны эти циклические внешние ключи. Допустим, table1 равно Employee, а table2 равно Customer. Конечно, большинство клиентов не являются сотрудниками (и наоборот). Но иногда клиент тоже может быть сотрудником. Это можно решить с помощью 3 таблиц:

Person
------
id
PRIMARY KEY: id

Employee
--------
personid
lastname
firstname
... other data
PRIMARY KEY: personid
FOREIGN KEY: personid
    REFERENCES Person(id)

Customer
--------
personid
creditCardNumber
... other data
PRIMARY KEY: personid
FOREIGN KEY: personid
    REFERENCES Person(id)

В описываемом вами сценарии у вас есть две таблицы Parent и Child, имеющие отношение 1:N. Затем вы хотите сохранить каким-либо образом наиболее эффективный (на основе определенного расчета) дочерний элемент для каждого родителя.

Будет ли это работать?:

Parent
------
id
PRIMARY KEY: id

Child
-----
id
parentid
... other data
PRIMARY KEY: id
FOREIGN KEY: parentid
    REFERENCES Parent(id)
UNIQUE KEY: (id, parentid)             --- needed for the FK below

BestChild
---------
parentid
childid
... other data
PRIMARY KEY: parentid
FOREIGN KEY: (childid, parentid)
    REFERENCES Child(id, parentid)

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

Вы можете найти BestChild для каждого родителя, присоединившись:

Parent
  JOIN BestChild
    ON Parent.id = BestChild.parentid
  JOIN Child
    ON BestChild.childid = Child.id

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

BestChild
---------
testid
parentid
childid
... other data
PRIMARY KEY: (testid, parentid)
FOREIGN KEY: (childid, parentid)
    REFERENCES Child(id, parentid)
FOREIGN KEY: testid
    REFERENCES Test(id)
1 голос
/ 14 июля 2011

Я бы создал таблицу с черными дырами и включил бы триггер, чтобы заботиться о вставках

CREATE TABLE bh_table12 (
  table1col varchar(45) not null,
  table2col varchar(45) not null
) ENGINE = BLACKHOLE

, и включил бы триггер, чтобы заботиться о вставках

DELIMITER $$

CREATE TRIGGER ai_bh_table12_each AFTER INSERT ON bh_table12 FOR EACH ROW
BEGIN
  DECLARE mytable1id integer;
  DECLARE mytable2id integer;

  SET foreign_key_checks = 0;
    INSERT INTO table1 (table1col, table2_id) VALUES (new.table1col, 0);
    SELECT last_insert_id() INTO mytable1id;
    INSERT INTO table2 (table2col, table1_id) VALUES (new.table2col, table1id);
    SELECT last_insert_id() INTO mytable2id;
    UPDATE table1 SET table2_id = mytable2id WHERE table1.id = mytable1id;
  SET foreign_key_checks = 1;
END $$

DELIMITER ;

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

Примечание к структуре таблицы
Обратите внимание, что если это таблица 1-на-1, вам нужно всего лишь поместить table2_id в table1, а не table1_id в table2 (или наоборот).
Если вам нужно запросить table1 на основе table2, вы можете просто использовать:

SELECT table1.* FROM table1
INNER JOIN table2 on (table2.id = table1.table2_id)
WHERE table2.table2col = 'test2'

Аналогично для обратного хода

SELECT table2.* FROM table2
INNER JOIN table1 on (table2.id = table1.table2_id)
WHERE table1.table1col = 'test1'

Ссылки:
http://dev.mysql.com/doc/refman/5.1/en/blackhole-storage-engine.html
http://dev.mysql.com/doc/refman/5.1/en/triggers.html

0 голосов
/ 05 марта 2013

Я чувствую, что это важный вопрос, и я не нашел 100% удовлетворительного ответа в сети. 2 ответа, которые вы дали, являются лучшими, которые я нашел, но они не являются удовлетворительными на 100%.

И вот почему:

  • Причина, по которой Эмилио не может поместить своего лучшего ребенка в родительский стол, довольно проста, я полагаю, потому что у меня одна и та же проблема: не каждый ребенок будет помечен как лучший ребенок родителей. Поэтому ему все равно нужно будет хранить информацию о других детях где-то еще. В этом случае он будет иметь некоторую информацию о лучших детях в таблице своих родителей и других детей в отдельной базе данных. Это огромный беспорядок. Например, в день, когда он хочет изменить структуру данных о детях, он должен изменить ее в обеих таблицах. Каждый раз, когда он пишет запрос обо всех дочерних элементах, он должен запрашивать обе таблицы и т. Д. *

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

Таким образом, я склонен думать, что решение с установкой Foreign_key_checks на ноль было бы лучшим, но вот проблема:

  • после установки значения foreign_key_checks обратно в 1 проверка целостности данных не выполняется. Таким образом, у вас есть риск сделать ошибки в то же время. Вы можете считать, что не будете, но все же это не очень чистое решение.
...