Как создать несколько один к одному - PullRequest
7 голосов
/ 07 февраля 2012

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

Inventory Table <*-----1> Storage Table <1-----1> Van Table
                              ^
                              1
                              |-------1> Warehouse Table

Таблица хранения используется, поскольку таблицы Van и Warehouse похожи, но как мне создать связь между таблицами Storage и Warehouse / Van? Было бы разумно, чтобы они были от 1 до 1, поскольку объект хранения может быть только 1 местом хранения и типом. У меня была ссылка на таблицу Van / Warehouse с первичным ключом StorageId, а затем я добавил ограничение, чтобы убедиться, что таблицы Van и Warehouse не имеют одинаковый StorageId, но, похоже, это можно сделать лучше.

Я вижу несколько способов сделать это, но все они кажутся неправильными, поэтому любая помощь будет хорошей!

Ответы [ 3 ]

17 голосов
/ 07 февраля 2012

Вы используете наследование (также известное в моделировании отношений сущностей как «подкласс» или «категория»).В общем, существует 3 способа представления его в базе данных:

  1. «Все классы в одной таблице»: Имеют только одну таблицу, «охватывающую» родительский и все дочерние классы(т. е. со всеми родительскими и дочерними столбцами), с ограничением CHECK, чтобы гарантировать, что правый поднабор полей не равен NULL (т. е. два разных потомка не «смешиваются»).
  2. «Конкретный класс дляtable ": У каждого ребенка есть отдельная таблица, но нет родительской таблицы.Это требует повторения родительских отношений (в вашем случае Inventory <- Storage) для всех дочерних элементов. </li>
  3. «Класс на таблицу»: Наличие родительской таблицы и отдельной таблицы для каждого ребенка,это то, что вы пытаетесь сделать.Это наиболее чисто, но может стоить некоторой производительности (в основном при изменении данных, не так много при запросах, потому что вы можете присоединиться напрямую от дочернего элемента и пропустить родительский).

Я обычно предпочитаю третий подход, нообеспечить присутствие и исключительность ребенка на уровне приложения.Выполнение обоих на уровне базы данных немного обременительно, но может быть сделано, если СУБД поддерживает отложенные ограничения.Например:

enter image description here

CHECK (
    (
        (VAN_ID IS NOT NULL AND VAN_ID = STORAGE_ID)
        AND WAREHOUSE_ID IS NULL
    )
    OR (
        VAN_ID IS NULL
        AND (WAREHOUSE_ID IS NOT NULL AND WAREHOUSE_ID = STORAGE_ID)
    )
)

Это обеспечит как исключительность (из-за CHECK), так и присутствие (из-за комбинации CHECK и FK1 / FK2) дочернего элемента.

К сожалению, MS SQL Server не поддерживает отложенные ограничения , но вы можете «спрятать» всю операцию за хранимыми процедурами и запретитьклиенты не могут изменять таблицы напрямую.


Только исключительность может быть применена без отложенных ограничений:

enter image description here

STORAGE_TYPE - это дискриминатор типа,обычно целое число для экономии места (в приведенном выше примере 0 и 1 «известны» вашему приложению и интерпретируются соответствующим образом).

Можно вычислить VAN.STORAGE_TYPE и WAREHOUSE.STORAGE_TYPE (он же «вычисляется»).) столбцы, чтобы сэкономить память и избежать необходимости в CHECK s.

--- EDIT ---

Вычисляемые столбцы будут работать под SQL Server следующим образом:

CREATE TABLE STORAGE (
    STORAGE_ID int PRIMARY KEY,
    STORAGE_TYPE tinyint NOT NULL,
    UNIQUE (STORAGE_ID, STORAGE_TYPE)
);

CREATE TABLE VAN (
    STORAGE_ID int PRIMARY KEY,
    STORAGE_TYPE AS CAST(0 as tinyint) PERSISTED,
    FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE)
);

CREATE TABLE WAREHOUSE (
    STORAGE_ID int PRIMARY KEY,
    STORAGE_TYPE AS CAST(1 as tinyint) PERSISTED,
    FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE)
);

-- We can make a new van.
INSERT INTO STORAGE VALUES (100, 0);
INSERT INTO VAN VALUES (100);

-- But we cannot make it a warehouse too.
INSERT INTO WAREHOUSE VALUES (100);
-- Msg 547, Level 16, State 0, Line 24
-- The INSERT statement conflicted with the FOREIGN KEY constraint "FK__WAREHOUSE__695C9DA1". The conflict occurred in database "master", table "dbo.STORAGE".

К сожалению, для SQL Server требуется вычисляемый column , который используется в иностранном ключе , чтобы быть ПЕРЕСЕРИРОВАННЫМ.Другие базы данных могут не иметь этого ограничения (например, виртуальные столбцы Oracle), что может сэкономить место для хранения.

1 голос
/ 07 февраля 2012

Мне почему-то кажется, что предметы инвентаря могут менять местоположение, поэтому я бы выбрал что-то вроде этого.

enter image description here

1 голос
/ 07 февраля 2012

Как вы говорите, есть много решений. Я бы порекомендовал начать с самого простого решения, а затем оптимизировать, если проблемы с производительностью или хранилищем станут проблемой. Самое простое решение (но не оптимальное с точки зрения хранения) будет иметь таблицу хранения, в которой есть столбец для типа хранения (указывающий, представляет ли строка фургон или склад), плюс столбцы для атрибутов фургона, а также атрибутов склада. В строке, которая представляет Van, столбцы для атрибутов Warehouse будут нулевыми. В строке, представляющей Склад, все столбцы для атрибутов Van будут нулевыми.

Таким образом, вы сокращаете количество таблиц и делаете ваши запросы красивыми и простыми. Будьте готовы пересмотреть свое решение, если склад будет ограничен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...