SQL: нормализация базы данных при сохранении ограничений

Предположим, у меня есть следующие таблицы:

     ____________________             ____________________
    |     Organisms      |           |       Species      |
    |--------------------|           |--------------------|
    |OrganismId (int, PK)|           |SpeciesId (int, PK) |
    |SpeciesId (int, FK) |∞---------1|Name (varchar)      |
    |Name (varchar)      |           |____________________|
    |____________________|                      1
              1                                 |
              |                                 |
              |                                 |
              ∞                                 ∞
    ______________________        ____________________          _______________
   | OrganismPropsValues  |      |   SpeciesProps     |        |     Props     |
   |----------------------|      |--------------------|        |---------------|
   |OrganismId (int, FK)  |      |PropId (int,PK,FK)  | ∞-----1|PropId (int,PK)|
   |PropId (int, FK)      |      |SpeciesId(int,PK,FK)|        |Name (varchar) |
   |Value (varchar)       |      |____________________|        |_______________|
   |______________________|                                             1
              ∞                                                         |
              |                                                         |
              -----------------------------------------------------------

Краткое объяснение того, что я пытаюсь изобразить здесь: предположим, у нас есть список видов, таких как кошка, собака, человек и т. Д. У нас также есть набор свойств (сокращенно Props, чтобы мне было удобнее вписать его в диаграмму), которые относятся к некоторым, но не обязательно ко всем видам - ​​например, это может быть длина хвоста (для видов с хвостами), цвет глаз (для видов с глазами) и т. д.

SpeciesProps - это таблица компоновщика, которая определяет, какие свойства применимы к какому виду - поэтому здесь у нас есть {Человек, Цвет глаз}, {Собака, Цвет глаз}, {Кошка, Цвет глаз}, {Собака, Длина хвоста}, {Кошка Длина хвоста. У нас нет {Human, Tail Length}, потому что Tail Length, очевидно, не является допустимым свойством, применимым к человеку.

Таблица "Организмы" содержит фактические "реализации" вида. Итак, здесь у нас могут быть {Человек, Боб}, {Собака, Руфус} и {Кошка, Феликс}.

Теперь моя проблема: в таблице OrganismPropsValues ​​я хочу сохранить "значения" свойств для каждого организма - например, для Боба я хочу сохранить {Bob, Eye Color, Blue}. Для Руфуса я бы хотел сохранить {Руфус, Цвет глаз, Коричневый} и {Руфус, Длина хвоста, 20} (аналогично Феликсу). Моя проблема, однако, заключается в том, что в схеме, которую я подробно описал, вполне возможно хранить {Bob, Tail Length, 10}, даже если кортеж {Human, Tail Length} не существует в SpeciesProps. Как я могу изменить эту схему, чтобы я мог применять ограничения, определенные в SpeciesProps в OrganismPropsValues, при этом поддерживая адекватную нормализацию?

4 ответа

Решение

Вы реализуете антипаттерн Entity-Attribute-Value. Это не может быть нормализованным дизайном базы данных, потому что он не реляционный.

Вместо этого я бы предложил шаблон проектирования наследования таблиц классов:

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

     ____________________             ____________________
    |     Organisms      |           |       Species      |
    |--------------------|           |--------------------|
    |OrganismId (int, PK)|           |SpeciesId (int, PK) |
    |SpeciesId (int, FK) |∞---------1|Name (varchar)      |
    |Name (varchar)      |           |____________________|
    |____________________|
              1
              |
              |
              1
     ______________________ 
    |    HumanOrganism     |
    |----------------------|
    |OrganismId (int, FK)  |
    |Sex      (enum)       |
    |Race     (int, FK)    |
    |EyeColor (int, FK)    |
    |....                  |
    |______________________|
    

Это означает, что вы создадите много таблиц, но рассматривайте это как компромисс со многими практическими преимуществами для хранения свойств относительно корректным способом:

  • Вы можете использовать типы данных SQL надлежащим образом, вместо того, чтобы рассматривать все в произвольной форме.
  • Вы можете использовать ограничения или таблицы поиска, чтобы ограничить определенные свойства предопределенным набором значений.
  • Вы можете сделать свойства обязательными (т. Е. NOT NULL) или использовать другие ограничения.
  • Данные и индексы хранятся более эффективно.
  • Запросы легче писать, а СУБД легче выполнять.

Подробнее об этом проекте см. Книгу Мартина Фаулера " Образцы архитектуры корпоративных приложений" или мою презентацию " Практические объектно-ориентированные модели в SQL" или мою книгу " Антипаттерны SQL: как избежать ошибок при программировании баз данных".

Хм...
Вот один из способов сделать это:
Добавьте SpeciesPropsId в таблицу SpeciesProps.
Замените PropId на SpeciesPropsId в таблице OrganismPropsValues.
Вам нужно будет немного изменить ограничения.
Необходимо добавить SpeciesProps в ограничение OrganismPropsValues.
Необходимо удалить OrganismPropsValues ​​для ограничения реквизита.

Технически вам не нужно удалять PropId из OrganismPropsValues, но если вы сохраните его, это сделает данные избыточными.

Всякий раз, когда у вас есть подобная ромбовидная зависимость, подумайте о том, чтобы уделять больше внимания составным ПЕРВИЧНЫМ КЛЮЧАМ.

В частности, идентифицировать Организм не просто OrganismId, но комбинацией SpeciesId а также OrganismSubId (вы все еще можете иметь OrganismId, но держите его в качестве альтернативного ключа - не показывайте здесь для краткости).

Как только вы это сделаете, ваша модель может выглядеть так:

ER модель

Ключевой момент, на который следует обратить внимание: SpeciesId "распространяется" вниз по обоим краям этого ромбовидного графа. Это то, что дает вам желаемое ограничение невозможности "присвоить значение" свойству, которое не было "объявлено" для данного вида.

Кстати, используйте единственное число при именовании ваших таблиц. Также рассмотрите возможность использования естественных первичных ключей (например, SpeciesName вместо SpeciesId как PK) - если все сделано правильно, это может значительно увеличить скорость ваших соединений (особенно в сочетании с кластеризацией).

Другим способом достижения этих ограничений было бы изменение PK Organism стол по капле OrganismId и добавив No, Затем сделайте ПК соединение (SpeciesId, No), Так, "Bob" было бы (Human, 1), "Rufus" было бы (Dog, 1), так далее.

Затем добавьте в OrganismPropsValues стол, SpeciesId и No (удаляя OrganismId.)

Это позволит изменить ФК с OrganismPropsValues в Props ссылаться SpeciesProps вместо:

     ____________________             ____________________
    |     Organisms      |           |       Species      |
    |--------------------|           |--------------------|
    |SpeciesId (int, FK) |           |SpeciesId (int, PK) |
    |No (int)            |∞---------1|Name (varchar)      |
    |Name (varchar)      |           |____________________|
    |PK (SpeciedId,No)   |                      1
    |____________________|                      |
              1                                 |
              |                                 |
              |                                 |
              ∞                                 ∞
    ______________________        ____________________          _______________
   | OrganismPropsValues  |      |   SpeciesProps     |        |     Props     |
   |----------------------|      |--------------------|        |---------------|
   |SpeciesId (int, PK)   |      |PropId (int,PK,FK)  | ∞-----1|PropId (int,PK)|
   |No (int, PK)          |      |SpeciesId(int,PK,FK)|        |Name (varchar) |
   |PropId (int, PK)      |      |____________________|        |_______________|
   |Value (varchar)       |                 1
   |FK (SpeciesId,No)     |                 |
   |FK (SpeciesId,PropId) |                 |
   |______________________|                 |
              ∞                             |
              |                             |
              -------------------------------
Другие вопросы по тегам