2013-10-24 7 views
7

Недавно я столкнулся с этим сценарием несколько раз в разных проектах. Вот схема из четырех таблиц, помеченная буквы:Конструкция отношений с четырьмя таблицами

 A 
    1/\ 1 
/ \ 
*/ \ * 
B  C 
1 \ /1 
    \ /
    * \/* 
    D 

В этом случае, возможно, для данных, чтобы стать противоречивыми, если ключи от B к A и C к A не совпадают для данный D.

для конкретного (составленного), например, представить себе A является Company, B является Employee, C является Project и D является WorkItem. В этом случае нет ничего, что могло бы остановить создание рабочего элемента, который утверждает, что его назначают человеку, который даже не работает для компании, которая владеет проектом.

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

Обратите внимание, что только отделяя одно из соединений, как от C к A не работает, потому что если нет D «s существуют для этого C вы не имели бы никакого способа отслеживания соединений обратно A.

+0

Хороший вопрос. Я всегда ставил меры предосторожности для предотвращения такого рода вещей на прикладном уровне. Будет интересно посмотреть, что другие скажут. –

+0

Я использую для ограничения такого рода ограничений в бизнес-правилах, а не триггерах. –

+0

Переопределите свои сущности: employee - >> person (помните: бывший сотрудник emplyee был сотрудником один раз) Возможно, потребуется временная ось (и несколько других дополнительных измерений, найдите скрытые размеры). – wildplasser

ответ

7

Используйте составные ключи (то есть ключ, содержащий несколько полей) для таблиц ниже по потоку. Тогда в D, вы можете использовать только одно поле для проведения КЛЮЧЕВОЙ:

[EDIT: Исправлена ​​глупая копия & паста ошибки в двойках 2 FK]

CREATE TABLE A (
    A_ID INTEGER PRIMARY KEY 
    -- Any other fields you want... 
); 

CREATE TABLE B (
    A_ID INTEGER REFERENCES A.A_ID, 
    B_ID INTEGER, 
    -- Any other fields you want... 
    PRIMARY KEY (A_ID, B_ID) 
); 

CREATE TABLE C (
    A_ID INTEGER REFERENCES A.A_ID, 
    C_ID INTEGER, 
    -- Any other fields you want... 
    PRIMARY KEY (A_ID, C_ID) 
); 

CREATE TABLE D (
    A_ID INTEGER, -- This field forms part of the FK for BOTH B and C 
    B_ID INTEGER, 
    C_ID INTEGER, 
    D_ID INTEGER, 
    -- Any other fields you want... 
    PRIMARY KEY (A_ID, B_ID, C_ID, D_ID), 
    FOREIGN KEY (A_ID, B_ID) REFERENCES B (A_ID, B_ID), 
    FOREIGN KEY (A_ID, C_ID) REFERENCES C (A_ID, C_ID) 
); 

Я не тестировал выше SQL, но вы надеетесь получить эту идею. Обратите внимание, что D не нуждается в третьем FK-ограничении для A, потому что это уже подразумевается другими FK (на самом деле это подразумевается отдельно каждым из них).

Проверка ссылочной целостности всегда лучше, чем триггеры - по крайней мере, с PostgreSQL это, и я подозреваю, что это верно со всеми РСУБД.

+0

Я считаю, что это единственный способ, но это адский ад (другие объекты могут потребовать даже более длинные ключи). Производительность, вероятно, тоже будет ужасной. –

+1

@LluisMartinez: Я ожидаю, что быстрее использовать одиночные целые (или, может быть, GUID) PK, но у меня не было проблем с производительностью с таблицами, организованными таким образом на PostgreSQL, некоторые из которых имеют миллионы строк. Фактически я нахожу, что упрощает отладку - при работе с записью из какой-либо зависимой таблицы ваша ПК немедленно сообщает вам об этом много, а не просто непрозрачный токен. Во-вторых, вы можете часто сортировать или группировать эти поля FK без необходимости соединения с родительскими таблицами. –

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