Двунаправленное ограничение внешнего ключа - PullRequest
6 голосов
/ 22 октября 2009

Я думаю о разработке схемы базы данных, подобной следующей:

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

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

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

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

Ответы [ 6 ]

11 голосов
/ 22 октября 2009

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

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

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

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

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

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

2 голосов
/ 22 октября 2009

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

2 голосов
/ 22 октября 2009

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

0 голосов
/ 04 июня 2012

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

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

Вот пример:

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 голосов
/ 22 октября 2009

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

0 голосов
/ 22 октября 2009

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

...