#database-design #cycle #relationship
#база данных-проектирование #цикл #взаимосвязь
Вопрос:
Циклы взаимодействия с базой данных пахнут плохим дизайном базы данных. Ниже приведена ситуация, в которой, я думаю, это невозможно предотвратить:
- у компании есть местоположения (город)
-
у компании есть продукты (Big Mac)
-
Продукты есть / отсутствуют в местах (в Саудовской Аравии нет бургеров с беконом)
Текущий дизайн позволил бы вам предлагать продукт, который не принадлежит этой компании, в местоположении, которое принадлежит этой компании.
Компания
1 — McDonalds
2 — Burger King
Расположение
1 — Нью-Йорк, здание 1 — McDonalds (1)
2 — Амстердам, здание 2 — Burger King (2)
Продукт
1 — Биг Мак — Макдональдс (1)
ProductLocation
1 — Биг Мак (1) — Амстердам, здание 2 (2)
McDonalds продает биг-маки, Burger King — нет, но, похоже, в их здании продают 🙂
Ситуация ухудшается, когда мы добавляем отношения к продукту, которые также зависят от местоположения.
Что я могу сделать, чтобы предотвратить цикл?
Как мне обеспечить целостность данных базы данных?
Комментарии:
1. «Ситуация становится хуже, когда мы добавляем отношения к продукту, которые также зависят от местоположения». Было бы еще сложнее, если бы они также зависели от компании. XYZ Burgers может подкупить саудовского чиновника и получить право продавать BLT в туристических местах. 🙂
Ответ №1:
Циклические зависимости не являются автоматически «плохим дизайном базы данных». С точки зрения концептуального моделирования, если такая зависимость точно представляет то, что вы пытаетесь смоделировать, то это не «неправильно».
К сожалению, ограничения SQL часто затрудняют или делают невозможным применение ограничений, которые являются циклическими. В SQL вам обычно приходится идти на компромисс, каким-либо образом нарушая ограничение или реализуя правило в процедурном коде, а не через ограничения базы данных.
Ответ №2:
Если мы начнем с Location
, Company
и Product
как независимых сущностей — как я думаю, вы пытались:
create table ProductAtLocation (
CompanyID integer
, LocationID integer
, ProductID integer
);
alter table ProductAtLocation
add constraint pk_ProdLoc primary key (CompanyID, LocationID, ProductID)
, add constraint fk1_ProdLoc foreign key (CompanyID, LocationID) references CompanyLocation (CompanyID, LocationID)
, add constraint fk2_ProdLoc foreign key (CompanyID, ProductID) references CompanyProduct (CompanyID, ProductID)
;
И если Product
является зависимым объектом (зависит от компании):
Комментарии:
1. Технически вы предлагаете решение моего вопроса, поэтому я пометил ваш ответ как ответ. С практической точки зрения, я предпочитаю сохранить циклы в своем дизайне БД (как показано dportas, циклы не обязательно плохие). Спасибо!
Ответ №3:
Что вам действительно нужно в качестве SQL «утверждения». Однако, к сожалению, ни одна текущая СУБД не поддерживает это. Утверждение было бы чем-то вроде:
assertion product_location_check
check (not exists (select null
from company_product_location cpl
where not exists
( select null
from company_products cp
join company_locations cl on c1.company_id = cp.company_id
and cp.product_id = cpl.product_id
and cl.location_id = cpl.location_id
and cp.company_id = cpl.company_id
)
)
);
При отсутствии таковых другой возможностью является настройка ключей таким образом, чтобы можно было проверять правило:
create table company_products
( company_id references companies
, product_id ...
, primary key (company_id, product_id)
);
create table company_locations
( company_id references companies
, location_id ...
, primary key (company_id, location_id)
);
create table company_product_locations
( company_id ...
, product_id ...
, location_id ...
, primary key (company_id, product_id, location_id)
, foreign key (company_id, product_id) references company_products)
, foreign key (company_id, location_id) references company_locations)
);
Это гарантирует, что каждый company_product_locations ссылается на продукт и местоположение, связанные с одной и той же компанией.
Еще одна возможность для сложных ограничений — использовать материализованные представления. Я здесь писал об этом в блоге в контексте Oracle.
Комментарии:
1. 1. Любой, кто хочет узнать больше о том, «как применять сложные ограничения» (то есть утверждения SQL), должен также (и особенно) прочитать «Прикладная математика для специалистов по базам данных», глава 11.
Ответ №4:
Я не согласен — это утверждение неверно:
Текущий дизайн позволил бы вам предлагать продукт, который не принадлежит этой компании
Если продукт не принадлежит компании, то у него не будет внешнего ключа для этой компании. У компании может быть много продуктов, но продукт может принадлежать только одной компании. Это отношение «один ко многим».
Что касается местоположения продукта, это звучит как отношение «многие ко многим»: продукт может предлагаться во многих местах, а местоположение может продавать много продуктов. Вам нужна таблица объединения Product_Location.
Обновить:
Добавленные вами записи только проясняют проблему. Местоположение — это больше, чем здание; McDonalds и Burger King могут находиться в одном здании, но они не находятся в одном и том же месте в этом здании. Вашей таблице местоположений понадобятся дополнительные столбцы помимо адреса улицы. Мои комментарии остаются в силе. Burger King не сможет продавать Биг Мак, если вы правильно его оформите. У вас это еще не получилось; отсюда ваша путаница.
Комментарии:
1. Я добавил несколько записей базы данных, чтобы прояснить проблему.
2. «Здание 1» описывает местоположение, где происходят продажи (уровень детализации не имеет значения). Дополнительные столбцы в таблице местоположений не будут способствовать повышению целостности базы данных.
Ответ №5:
Частично проблема заключается в том, что и McDonald’s, и Burger King продают продукты под названием «гамбургер» и «чизбургер» и (я думаю) «двойной чизбургер». Таким образом, информация, которую вы храните в ProductLocation, является неполной.
Product
--
Big Mac McDonald's
Hamburger McDonald's
Hamburger Burger King
ProductLocation
Big Mac McDonald's New York, building 1
Hamburger McDonald's New York, building 1
Hamburger Burger King Amsterdam, building 2
И Даффимо прав, когда говорит, что «Местоположение — это больше, чем здание».
Вот один из способов реализовать эти ограничения. Я убрал идентификационные номера, потому что они имеют тенденцию скрывать, что происходит на самом деле.
create table company (
co_name varchar(15) primary key
);
insert into company values
('McDonald''s'),
('Burger King');
create table location (
loc_name varchar(30) primary key,
co_name varchar(15) not null references company (co_name),
unique (loc_name, co_name)
);
insert into location values
('New York, building 1', 'McDonald''s'),
('Amsterdam, building 2', 'Burger King');
create table product (
co_name varchar(15) not null references company (co_name),
product_name varchar(15) not null,
primary key (co_name, product_name)
);
insert into product values
('McDonald''s', 'Big Mac'),
('McDonald''s', 'Hamburger'),
('McDonald''s', 'Cheeseburger'),
('Burger King', 'Hamburger'),
('Burger King', 'Cheeseburger');
create table product_location (
loc_name varchar(30) not null references location (loc_name),
co_name varchar(15) not null,
product_name varchar(15) not null,
foreign key (co_name, product_name) references product (co_name, product_name),
foreign key (loc_name, co_name) references location (loc_name, co_name),
primary key (loc_name, co_name, product_name)
);
insert into product_location values
('Amsterdam, building 2', 'Burger King', 'Cheeseburger');
Обратите внимание на перекрывающиеся внешние ключи в product_location. Перекрывающиеся внешние ключи гарантируют, что компания, идентифицированная с местоположением, и компания, идентифицированная с продуктом, являются одной и той же компанией. Теперь следующая ВСТАВКА завершится ошибкой из-за нарушения ограничения внешнего ключа.
insert into product_location values
('Amsterdam, building 2', 'McDonald''s', 'Cheeseburger');