2009-10-22 2 views
4

Я думаю о разработке схемы базы данных, подобную следующему:Двунаправленного ограничение внешнего ключа

Person (
    PersonID int primary key, 
    PrimaryAddressID int not null, 
    ... 
) 

Address (
    AddressID int primary key, 
    PersonID int not null, 
    ... 
) 

Person.PrimaryAddressID и Address.PersonID бы внешние ключи к соответствующим таблицам.

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

ответ

10

«Я считаю, что это невозможно. Вы не можете создать запись адреса, пока не узнаете идентификатор человека, и вы не сможете вставить запись человека, пока не узнаете адрес AddressId для поля PrimaryAddressId».

На первый взгляд, это утверждение кажется таким привлекательным. Однако он довольно прост.

Это очень распространенная проблема, с которой поставщики СУБД SQL пытались атаковать уже несколько десятилетий.

Ключ в том, что все проверки ограничений должны быть «отложены» до тех пор, пока не будут выполнены обе вставки. Это может быть достигнуто в разных формах. Операции с базами данных могут предлагать возможность сделать что-то вроде «Отложенная проверка ограничений на отказ SET», и вы закончили (не потому ли, что в этом конкретном примере вам, вероятно, придется очень тяжело вносить свой проект в порядок чтобы иметь возможность просто ОПРЕДЕЛАТЬ два ограничения FK, потому что один из них просто НЕ является «истинным» FK в смысле SQL!).

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

В своей работе Chris Date & Хью Дарвен описывает то, что является истинным решением проблемы: множественное назначение. Это, по сути, возможность составить несколько отдельных операторов обновления и заставить СУБД действовать, как если бы это было одно единственное утверждение. Реализации этой концепции существуют, но вы не найдете никаких разговоров SQL.

0

Второй FK (PersonId от адреса к человеку) является слишком ограничительным, ИМХО. Вы предлагаете, чтобы на одном адресе мог быть только один человек?

+0

Да, каждый адрес относится только к одному человеку. Я не думаю, что это будет иметь большое значение, хотя, если бы я превратил его в отношения «многие ко многим». – jthg

+0

Вы всегда будете делиться реестрами? Может ли г-н Лицо и г-жа Лич делить один адрес или иметь отдельные (но идентичные) адресные записи? – Ray

+0

Нет, записи адресов никогда не будут доступны. – jthg

0

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

+0

Каждый человек может иметь несколько адресов. – jthg

+0

Но у них может быть только 1 первичный адрес, правильно? – GenericTypeTea

+0

Да. Это было моим аргументом в пользу размещения столбца PrimaryAddressID в таблице Person, вместо того, чтобы иметь столбец IsPrimary в таблице Address. – jthg

2

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

+0

Это определенно дизайн, который я видел раньше - за исключением того, что в приложении не задействованы триггеры и приложения. Помимо дополнительного обслуживания, я не уверен, насколько надежными будут триггеры. Вы уверены, что они охватывают все возможные сценарии? – jthg

+0

Да, я уверен, и наши триггеры были очень тщательно проверены и работали без проблем годами. Согласитесь, что использование триггеров для этого - сложный процесс (но он делает это в приложении, и это гораздо больший риск для целостности данных), и они должны быть тщательно протестированы (особенно с несколькими вставками набора записей/обновлениями/удалениями). Триггеры имеют решающее значение для нашей системы, так как мы получаем данные из многих приложений и многих других источников (мы импортируем несколько сотен данных в неделю). Этот процесс должен быть включен, если вы хотите обеспечить целостность данных. – HLGEM

+0

Лучшим подходом вместо триггеров было бы уникальное ограничение ключа для вычисленного столбца «UniqueColumn в случае, когда IsPrimary then PersonID else null end» – MichaelD

2

Это прекрасный пример отношений «многие ко многим». Чтобы разрешить это, вы должны иметь промежуточную таблицу PERSON_ADDRESS. Другими словами;

PERSON table 
person_id (PK) 

ADDRESS table 
address_id (PK) 

PERSON_ADDRESS 
person_id (FK) <= PERSON 
address_id (FK) <= ADDRESS 
is_primary (BOOLEAN - Y/N) 

Таким образом, вы можете назначить несколько адресов для человека, а также повторное использование адресных записей в нескольких лиц (для членов семьи, сотрудников одной и той же компании и т.д.). Используя поле is_primary в таблице PERSON_ADDRESS, вы можете определить, является ли эта комбинация person_addrees основным адресом для человека.

+0

Я не думаю, что это фактически решает проблему - она ​​просто избегает этого, расслабляя отношения. Это похоже на сброс ограничения внешнего ключа на адрес.PersonID – jthg

+0

Затем вы должны обеспечить соблюдение этого бизнес-правила (каждый человек должен иметь первичный адрес) с помощью триггера в таблице PERSON. Модель, которую я описал, должна поддерживать это бизнес-правило. – mevdiven

-1

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

Я сделал это так для моего «конкретного собственного уникального и нестандартного» бизнес-потребности (= (Бог Я начинаю звучать как SQL DDL, даже когда я говорю)

Вот exaxmple:.

CREATE TABLE IF NOT EXISTS PERSON(
    ID INT, 
    CONSTRAINT PRIMARY KEY (ID), 
    ADDRESS_ID INT NOT NULL DEFAULT 1, 
    DESCRIPTION VARCHAR(255), 
    CONSTRAINT PERSON_UQ UNIQUE KEY (ADDRESS_ID, ...)); 

INSERT INTO PERSON(ID, DESCRIPTION) 
    VALUES (1, 'GOVERNMENT'); 

CREATE TABLE IF NOT EXISTS ADDRESS(
    ID INT, 
    CONSTRAINT PRIMARY KEY (ID), 
    PERSON_ID INT NOT NULL DEFAULT 1, 
    DESCRIPTION VARCHAR(255), 
    CONSTRAINT ADDRESS_UQ UNIQUE KEY (PERSON_ID, ...), 
    CONSTRAINT ADDRESS_PERSON_FK FOREIGN KEY (PERSON_ID) REFERENCES PERSON(ID)); 

INSERT INTO ADDRESS(ID, DESCRIPTION) 
    VALUES (1, 'ABANDONED HOUSE AT THIS ADDRESS'); 

ALTER TABLE PERSON ADD CONSTRAINT PERSON_ADDRESS_FK FOREIGN KEY (ADDRESS_ID) REFERENCES ADDRESS(ID); 

< ... жизнь продолжается ... обеспечивают ли вы и адрес или не к лицу и наоборот>

Я определил одну таблицу, затем другую таблицу, ссылающуюся на первую, а затем изменил первый, чтобы отразить ссылку на вторую (которая не существовала во время создания первой таблицы). Он не предназначен для конкретной базы данных; если мне это нужно, я просто попробую, и если это сработает, я буду использовать его, если не тогда, я попытаюсь избежать необходимости в дизайне (я не всегда могу это контролировать, иногда дизайн передается мне как есть) , если у вас есть адрес без человека, то он принадлежит «правительству». Если у вас есть «бездомный человек», тогда он получает адрес «заброшенный дом». Я запускаю процесс, чтобы определить, в каких домах нет пользователей.

+0

Стандартное решение этого ** - ** использовать отложенные ограничения (как упоминал Эрвин). Однако не все СУБД достаточно продвинуты, чтобы поддержать это. –

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