Подумайте о том, как вы бы смоделировали операции в объектно-ориентированном дизайне: операции будут подклассами общего суперкласса Operation
. Каждый подкласс должен иметь обязательные элементы объекта для соответствующего оборудования, необходимого для этой операции.
Способ моделирования этого с помощью SQL - наследование таблицы классов. Создайте общую супертаблицу:
CREATE TABLE Operation (
operation_id SERIAL PRIMARY KEY,
operation_type CHAR(1) NOT NULL,
UNIQUE KEY (operation_id, operation_type),
FOREIGN KEY (operation_type) REFERENCES OperationTypes(operation_type)
);
Затем для каждого типа операции определите подтаблицу со столбцом для каждого требуемого типа оборудования. Например, OperationFoo
имеет столбец для каждого из equipA
и equipB
. Поскольку они оба необходимы, столбцы равны NOT NULL
. Ограничьте их правильными типами, создав супертаблицу Class Table Inheritance для оборудования.
CREATE TABLE OperationFoo (
operation_id INT PRIMARY KEY,
operation_type CHAR(1) NOT NULL CHECK (operation_type = 'F'),
equipA INT NOT NULL,
equipB INT NOT NULL,
FOREIGN KEY (operation_id, operation_type)
REFERENCES Operations(operation_d, operation_type),
FOREIGN KEY (equipA) REFERENCES EquipmentA(equip_id),
FOREIGN KEY (equipB) REFERENCES EquipmentB(equip_id)
);
Таблица OperationBar
не требует оборудования, поэтому в нем нет столбцов экипировки:
CREATE TABLE OperationBar (
operation_id INT PRIMARY KEY,
operation_type CHAR(1) NOT NULL CHECK (operation_type = 'B'),
FOREIGN KEY (operation_id, operation_type)
REFERENCES Operations(operation_d, operation_type)
);
В таблице OperationBaz есть одно необходимое оборудование equipA
, и тогда хотя бы одно из equipB
и equipC
должно быть NOT NULL
. Используйте для этого ограничение CHECK
:
CREATE TABLE OperationBaz (
operation_id INT PRIMARY KEY,
operation_type CHAR(1) NOT NULL CHECK (operation_type = 'Z'),
equipA INT NOT NULL,
equipB INT,
equipC INT,
FOREIGN KEY (operation_id, operation_type)
REFERENCES Operations(operation_d, operation_type)
FOREIGN KEY (equipA) REFERENCES EquipmentA(equip_id),
FOREIGN KEY (equipB) REFERENCES EquipmentB(equip_id),
FOREIGN KEY (equipC) REFERENCES EquipmentC(equip_id),
CHECK (COALESCE(equipB, equipC) IS NOT NULL)
);
Точно так же в таблице OperationQuux
вы можете использовать ограничение CHECK
, чтобы убедиться, что хотя бы один ресурс оборудования каждой пары не равен нулю:
CREATE TABLE OperationQuux (
operation_id INT PRIMARY KEY,
operation_type CHAR(1) NOT NULL CHECK (operation_type = 'Q'),
equipA INT,
equipB INT,
equipC INT,
equipD INT,
FOREIGN KEY (operation_id, operation_type)
REFERENCES Operations(operation_d, operation_type),
FOREIGN KEY (equipA) REFERENCES EquipmentA(equip_id),
FOREIGN KEY (equipB) REFERENCES EquipmentB(equip_id),
FOREIGN KEY (equipC) REFERENCES EquipmentC(equip_id),
FOREIGN KEY (equipD) REFERENCES EquipmentD(equip_id),
CHECK (COALESCE(equipA, equipB) IS NOT NULL AND COALESCE(equipC, equipD) IS NOT NULL)
);
Это может показаться большим трудом. Но вы спросили, как это сделать в SQL. Лучший способ сделать это в SQL - использовать декларативные ограничения для моделирования бизнес-правил. Очевидно, это требует, чтобы вы создавали новую подтаблицу каждый раз, когда вы создаете новый тип операции. Это лучше всего, когда операции и бизнес-правила никогда (или почти никогда) не меняются. Но это может не соответствовать требованиям вашего проекта. Большинство людей говорят: «Но мне нужно решение, которое не требует изменения схемы».
Большинство разработчиков, вероятно, не используют наследование таблиц классов. Чаще всего они просто используют структуру таблицы «один ко многим», как упоминали другие люди, и реализуют бизнес-правила исключительно в коде приложения. То есть ваше приложение содержит код для вставки только оборудования, подходящего для каждого типа операции.
Проблема с использованием логики приложения заключается в том, что оно может содержать ошибки и вставлять данные, не соответствующие бизнес-правилам. Преимущество наследования таблиц классов состоит в том, что с помощью хорошо разработанных ограничений СУБД обеспечивает последовательную целостность данных. У вас есть уверенность в том, что база данных буквально не может хранить неверные данные.
Но это также может быть ограничивающим фактором, например, если ваши бизнес-правила меняются, и вам нужно скорректировать данные. Распространенное решение в этом случае - написать сценарий для выгрузки всех данных, изменить схему и затем перезагрузить данные в разрешенной форме (Извлечь, преобразовать и загрузить = ETL).
Итак, вам нужно решить: вы хотите закодировать это на уровне приложения или на уровне схемы базы данных? Есть законные причины использовать любую стратегию, но в любом случае это будет сложно.
Повторите свой комментарий: похоже, вы говорите о хранении выражений в виде строк в полях данных. Я рекомендую не делать этого. База данных предназначена для хранения данных, а не кода. Вы можете выполнять некоторую ограниченную логику в ограничениях или триггерах, но код принадлежит вашему приложению.
Если у вас слишком много операций для моделирования в отдельных таблицах, смоделируйте их в коде приложения. Хранение выражений в столбцах данных и ожидание того, что SQL будет использовать их для оценки запросов, - все равно что проектировать приложение с интенсивным использованием eval()
.
person
Bill Karwin
schedule
12.02.2010