Что-то вроде наследования в дизайне базы данных
Предположим, что вы создавали базу данных для хранения данных об авариях различных транспортных средств. Вы хотите хранить данные краш-тестов для катеров, автомобилей и карт-картов.
Вы можете создать три отдельные таблицы: 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-й) касается полей в зависимости от первичного ключа и только первичного ключа. Правило делает предположение, что первичный ключ был правильно идентифицирован (неверно определить первичный ключ слишком просто).