SQL: нормализация базы данных при сохранении ограничений - PullRequest
5 голосов
/ 25 августа 2011

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

     ____________________             ____________________
    |     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
              ∞                                                         |
              |                                                         |
              -----------------------------------------------------------

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

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

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

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

Ответы [ 4 ]

4 голосов
/ 25 августа 2011

Вы используете антипаттерн 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 соответствующим образом, вместо того, чтобы рассматривать все как varchar в свободной форме.
  • Вы можете использовать ограничения или таблицы поиска, чтобы ограничить определенные свойства предопределенным набором значений.
  • Вы можете сделать свойства обязательными (т. Е. NOT NULL) или использовать другие ограничения.
  • Данные и индексыхранится более эффективно.
  • Запросы легче писать и выполнять СУБД.

Подробнее об этом проекте см. книгу Мартина Фаулера Шаблоны предприятияАрхитектура приложения , или моя презентация Практические объектно-ориентированные модели в SQL , или моя книга, Антипаттерны SQL: Avoвыявление подводных камней программирования баз данных .

2 голосов
/ 25 августа 2011

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

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

Как только вы это сделаете, ваша модель может быть сделана длявыглядеть следующим образом:

ER Model

Здесь необходимо отметить, что SpeciesId «распространяется» вниз по обоим краям этого ромбовидного графа.Это то, что дает вам желаемое ограничение невозможности «присвоить значение» свойству, которое не было «объявлено» для данного вида.

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

2 голосов
/ 25 августа 2011

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

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

1 голос
/ 25 августа 2011

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

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

Это позволит изменить FK с 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) |                 |
   |______________________|                 |
              ∞                             |
              |                             |
              -------------------------------
...