Что-то вроде наследования в дизайне базы данных

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

Вы можете создать три отдельные таблицы: SpeedboatTests, CarTests и GokartTests. Но многие ваши столбцы будут одинаковыми в каждой таблице (например, идентификатор сотрудника того, кто выполнил тест, направление столкновения (спереди, сбоку, сзади) и т.д.). Однако множество столбцов будут разными, поэтому вы не хотите просто поместить все тестовые данные в одну таблицу, потому что у вас будет довольно много столбцов, которые всегда будут равны нулю для скоростных катеров, и многие из них будут всегда быть нулевым для автомобилей, и немало, которые всегда будут нулевыми для карточных карт.

Скажем, вы также хотите сохранить некоторую информацию, которая напрямую не связана с тестами (например, идентификатор сотрудника дизайнера проверяемой вещи). Эти столбцы не кажутся правильными для включения в таблицу "Тесты" вообще, особенно потому, что они будут повторяться для всех тестов на одном и том же транспортном средстве.

Позвольте мне проиллюстрировать одно возможное расположение таблиц, чтобы вы могли видеть затронутые вопросы.

Speedboats
id | col_about_speedboats_but_not_tests1 | col_about_speedboats_but_not_tests2

Cars
id | col_about_cars_but_not_tests1 | col_about_cars_but_not_tests2

Gokarts
id | col_about_gokarts_but_not_tests1 | col_about_gokarts_but_not_tests2

Tests
id | type | id_in_type | col_about_all_tests1 | col_about_all_tests2
(id_in_type will refer to the id column of one of the next three tables,
depending on the value of type)

SpeedboatTests
id | speedboat_id | col_about_speedboat_tests1 | col_about_speedboat_tests2

CarTests
id | car_id | col_about_car_tests1 | col_about_car_tests2

GokartTests
id | gokart_id | col_about_gokart_tests1 | col_about_gokart_tests2

Что хорошего/плохого в этой структуре и что было бы предпочтительным способом реализации чего-то подобного?

Что делать, если есть также информация, которая относится ко всем автомобилям, которые вы предпочитаете иметь в таблице транспортных средств? Будет ли таблица CarTests выглядеть так...

id | vehicle_id | ...

With a Vehicles table like this:
id | type | id_in_type
(with id_in_type pointing to the id of either a speedboat, car, or go-kart)

Это просто становится королевским беспорядком. Как СЛЕДУЕТ установить что-то подобное?

Ответ 1

Конструкция type и id_in_type называется Полиморфные ассоциации. Этот дизайн несколько раз нарушает правила нормализации. Если ничего другого, это должен быть красный флаг, который нельзя объявить как ограничение реального внешнего ключа, поскольку id_in_type может ссылаться на любую из нескольких таблиц.

Вот лучший способ определения ваших таблиц:

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

Здесь образец DDL:

CREATE TABLE Vehicles (
 vehicle_id INT AUTO_INCREMENT PRIMARY KEY
);

CREATE TABLE Speedboats (
 vehicle_id INT PRIMARY KEY,
 col_about_speedboats_but_not_tests1 INT,
 col_about_speedboats_but_not_tests2 INT,
 FOREIGN KEY(vehicle_id) REFERENCES Vehicles(vehicle_id)
);

CREATE TABLE Cars (
 vehicle_id INT PRIMARY KEY,
 col_about_cars_but_not_tests1 INT,
 col_about_cars_but_not_tests2 INT,
 FOREIGN KEY(vehicle_id) REFERENCES Vehicles(vehicle_id)
);

CREATE TABLE Gokarts (
 vehicle_id INT PRIMARY KEY,
 col_about_gokarts_but_not_tests1 INT,
 col_about_gokarts_but_not_tests2 INT,
 FOREIGN KEY(vehicle_id) REFERENCES Vehicles(vehicle_id)
);

CREATE TABLE Tests (
 test_id INT AUTO_INCREMENT PRIMARY KEY,
 col_about_all_tests1 INT,
 col_about_all_tests2 INT
);

CREATE TABLE SpeedboatTests (
 test_id INT PRIMARY KEY,
 vehicle_id INT NOT NULL,
 col_about_speedboat_tests1 INT,
 col_about_speedboat_tests2 INT,
 FOREIGN KEY(test_id) REFERENCES Tests(test_id),
 FOREIGN KEY(vehicle_id) REFERENCES Speedboats(vehicle_id)
);

CREATE TABLE CarTests (
 test_id INT PRIMARY KEY,
 vehicle_id INT NOT NULL,
 col_about_car_tests1 INT,
 col_about_car_tests2 INT,
 FOREIGN KEY(test_id) REFERENCES Tests(test_id),
 FOREIGN KEY(vehicle_id) REFERENCES Cars(vehicle_id)
);

CREATE TABLE GokartTests (
 test_id INT PRIMARY KEY,
 vehicle_id INT NOT NULL,
 col_about_gokart_tests1 INT,
 col_about_gokart_tests2 INT,
 FOREIGN KEY(test_id) REFERENCES Tests(test_id),
 FOREIGN KEY(vehicle_id) REFERENCES Gokarts(vehicle_id)
);

Вы также можете объявить Tests.vehicle_id, который ссылается на Vehicles.vehicle_id и избавиться от внешних ключей vehicle_id в каждой таблице подтипов теста, но это позволит аномалии, такие как тест на скоростном катере, который ссылается на идентификатор gokart.

Ответ 2

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

http://martinfowler.com/eaaCatalog/singleTableInheritance.html

http://martinfowler.com/eaaCatalog/classTableInheritance.html

http://martinfowler.com/eaaCatalog/concreteTableInheritance.html

Если количество дополнительных полей/столбцов для подклассов невелико, то наследование одной таблицы обычно является самым простым в работе.

Если вы используете PostgreSQL для своей базы данных и хотите привязать себя к специфичной для базы данных функции, она напрямую поддерживает наследование таблицы:

http://www.postgresql.org/docs/8.3/static/ddl-inherit.html

Ответ 3

Я разбил бы его на разные таблицы, например. Автомобиль (ID, тип и т.д.) VehicleAttributes() VehicleID, AttributeID, Value), CrashTestInfo (VehicleID, CrashtestID, Дата и т.д.) CrashtestAttributes (CrashTestID, AttributeID, Value)

Или вместо атрибутов, отдельные таблицы для каждого набора похожих деталей, которые должны быть записаны.

Ответ 4

Сделайте поиск по Google в реляционном моделировании gen-spec. Вы найдете статьи о том, как настроить таблицы, в которых хранятся атрибуты обобщенного объекта (какие программисты OO могли бы назвать суперкласс), отдельные таблицы для каждого из специализированных объектов (подклассы) и способы использования внешних ключей для его связывания все вместе.

Лучшие статьи, IMO, обсуждают gen-spec с точки зрения моделирования ER. Если вы знаете, как преобразовать модель ER в реляционную модель, а оттуда в SQL-таблицы, вы будете знать, что делать, показывая, как моделировать ген-spec в ER.

Если вы просто Google на "gen-spec", большая часть того, что вы увидите, ориентирована на объекты, а не реляционная. Этот материал может быть полезен, если вы знаете, как преодолеть несоответствие реляционного импеданса объекта.

Ответ 5

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

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

Ответ 6

Ваша конструкция разумна и соблюдает правильные правила нормализации. Возможно, вам не хватает таблицы автомобилей с идентификатором и типом транспортного средства (т.е. "Родитель" для скоростных катеров, автомобилей и Gokarts... там, где вы будете хранить такие вещи, как "DesignedByUserId" ). Между таблицей транспортного средства и таблицей скоростных катков есть соотношение "один к одному", а между транспортным средством и скоростным катером/автомобилями/GoKarts существует соотношение 1 и только 1 (т.е. Транспортное средство может иметь только 1 запись для катера, автомобили или пойти на карты)... хотя большинство db не предлагают для этого простой механизм обеспечения безопасности.

Одно правило нормализации, которое помогает идентифицировать эти вещи, состоит в том, что поле должно зависеть только от первичного ключа таблицы. В консолидированной таблице, где результаты испытаний на катере, автомобилях и гокарте хранятся вместе, поля, относящиеся к автомобилям, зависят не только от даты испытания, но также от идентификатора вешица и типа транспортного средства. Первичным ключом для таблицы результатов испытаний является дата теста + идентификатор автомобиля, а тип транспортного средства не является тем, что делает уникальную строку данных испытаний (т.е. Существует ли вообще какое-либо испытание для проведения теста 01/01/200912: 30pm на одном конкретном транспортном средстве это как скоростной катер, так и автомобиль... нет... не может быть сделано).

Я не объясняю правило нормировки особенно хорошо... но правила 3-й/4-й/5-й нормальные формы всегда меня смущают, когда я читаю формальные описания. Один из них (3-й/4-й/5-й) касается полей в зависимости от первичного ключа и только первичного ключа. Правило делает предположение, что первичный ключ был правильно идентифицирован (неверно определить первичный ключ слишком просто).