2010-02-08 5 views
3

У меня есть две таблицы в моей базе данных, Operation и Equipment. Для операции требуется ноль или более атрибутов. Тем не менее, нет какой-то логика в том, как атрибуты приписываются:Вопрос дизайна: Фильтрующие атрибуты, SQL

  • Операция Foo требует оборудования A и B
  • Операция Bar требует никакого оборудования
  • Операция Baz требует оборудования B и либо C или D
  • Операция Quux требуется оборудование (A или B) и ()или D)

Каков наилучший способ представить это в SQL?

Я уверен, что люди это делали раньше, но я не знаю, с чего начать.

(FWIW, мое приложение построено с Python и Django.)

Update 1: Там будет около тысячи Operation строк и около тридцати Equipment строк. Информация поступает в CSV формы похожи на приведенное выше описание: Quux, (A & B) | (C & D)

Update 2: уровня конъюнкция & дизъюнкция не должна быть слишком глубокой. Пример Quux, вероятно, самый сложный, хотя, кажется, есть случай A | (D & E & F).

+0

Являются ли оборудование класса A/B/C/D оборудованием или конкретными предметами оборудования? т. е. будет ли таблица оборудования иметь ровно четыре записи или большое количество записей четырех различных типов? –

+0

В таблице «Оборудование» будет много строк (около 30). –

+0

Любите значок ResEdit! –

ответ

10

Подумайте о том, как вы бы моделировать операции в ОО дизайн: операции будут подклассами общего суперкласса Operation. Каждый подкласс должен иметь обязательные члены объекта для соответствующего оборудования, необходимого для этой операции.

Способ моделирования этого с помощью SQL - Class Table Inheritance. Создать общую супер-таблицу:

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 для оборудования.

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 не требует никакого оборудования, так что это не имеет EQUIP столбцы:

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 - использовать декларативные ограничения для моделирования ваших бизнес-правил. Очевидно, это требует, чтобы вы создавали новую подкатегорию каждый раз, когда вы создаете новый тип операции. Это лучше всего, когда операции и бизнес-правила никогда (или почти никогда) не меняются. Но это может не соответствовать вашим требованиям к проекту. Большинство людей говорят: «Но мне нужно решение, которое не требует изменений схемы».

Большинство разработчиков, вероятно, не выполняют Наследование классов. Чаще всего они используют структуру таблиц «один-ко-многим», как упомянули другие люди, и внедряют бизнес-правила исключительно в код приложения. То есть ваше приложение содержит код для вставки только оборудования, соответствующего каждому типу операции.

Проблема с использованием логики приложения заключается в том, что она может содержать ошибки и может вставлять данные, которые не соответствуют бизнес-правилам. Преимущество Наследования классов таблицы заключается в том, что с хорошо разработанными ограничениями РСУБД последовательно выполняет целостность данных. У вас есть уверенность, что база данных буквально не может хранить неправильные данные.

Но это также может быть ограничением, например, если ваши бизнес-правила меняются, и вам необходимо настроить данные. Общим решением в этом случае является запись сценария для выгрузки всех данных, изменения вашей схемы, а затем перезагрузки данных в форме, которая теперь разрешена (Extract, Transform, and Load = ETL).

Итак, вы должны решить: хотите ли вы кодировать это на уровне приложения или на уровне схемы базы данных? Есть законные причины использовать любую стратегию, но она будет сложной в любом случае.


Re: Вы, кажется, говорите об хранении выражений в виде строк в полях данных. Я рекомендую против делая это. База данных предназначена для хранения данных, а не кода. Вы можете использовать ограниченную логику в ограничениях или триггерах, но код принадлежит вашему приложению.

Если у вас слишком много операций для моделирования в отдельных таблицах, то моделируйте его в коде приложения. Хранение выражений в столбцах данных и ожидание использования SQL для оценки запросов было бы похожим на разработку приложения при интенсивном использовании eval().

+0

P.S .: MySQL не поддерживает контрольные ограничения, поэтому вам придется реализовать их с помощью триггеров или внешних ключей. –

+0

Yeesh. Вы правы - я спросил, как это сделать в SQL. Тем не менее, я обновил сообщение, объяснив, что будет тысяча видов «Операций». Я не могу не думать о том, что сокращение вашего предложения до таблиц «OperationConjunction» и «OperationDisjunction» может работать. –

2

Я думаю, что у вас должно быть отношение «один-ко-многим» или «много-ко-многим» между Operation и Equipment, в зависимости от того, есть ли один вход Equipment на единицу оборудования или на тип оборудования.

Я бы посоветовал не вводить бизнес-логику в вашу схему базы данных, поскольку бизнес-логика может быть изменена, и вам не нужно будет менять вашу схему в ответ.

+0

Спасибо, но я не помещаю бизнес-логику в базу данных - я пытаюсь представить условия, в которых может быть выбран объект. –

+1

Правильно, а что определяет эти условия? – danben

+0

Внешний источник: много людей. См. Обновление 1 выше. –

0

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

Операция

  • ID
  • othercolumn

Оборудование

  • ID
  • othercolumn

Operation_Equipment_Link

  • OperationID
  • EquipmentID

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

1

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

OperationEquipmentGroup 
    id int 
    operation_id int 
    is_conjuction bit 

OperationEquipment 
    id int 
    operation_equipment_group_id int 
    equipment_id 

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

0

В дополнение к предложению Nicholai, я решил аналогичную проблему следующим образом:

Таблица Операция имеет дополнительное поле «OperationType»

Таблица Оборудование имеет дополнительное поле «EquipmentType»

У меня есть дополнительная таблица «DefaultOperationEquipmentType», указывающая, какой тип EquipmentType должен быть включен с каждым типом OperationType, например

OperationType EquipmentType 
==============.=============. 
Foo_Type  A_Type 
Foo_Type  B_Type 
Baz_Type  B_Type 
Baz_Type  C_Type 

Мое приложение не нуждается в сложных условиях, таких как (А или В), потому что в моей бизнес-логики как альтернативного оборудования принадлежат к тому же типу оборудования, например, в среде ПК я мог бы иметь оборудование Mouse (A) или трекбол (B), но они оба принадлежат к EquipmentType «PointingDevice_Type»

Надежда, что помогает

+0

Ах, но я действительно требую сложной логики. Представьте себе, что в вашей среде, где вам нужно «Клавиатура И (мышь или джойстик ИЛИ WacomTablet)» –

+0

, это все о том, как вы настраиваете типы оборудования (или классы, если хотите); И реализуется путем назначения нескольких типов оборудования одному типу операции, OR реализуется несколькими устройствами, назначенными одному типу оборудования. По мере того, как ключ переходит (оборудование_тип, оборудование), одно оборудование может быть членом большего количества типов оборудования - например, типы CADPointers содержат (Tablet, Digipen, Mouse), OfficePointers содержат (мышь, трекбол) и клавиатура содержат (GermanKey, USKey). .. – MikeD

+0

... теперь вы можете определить тип CADPCType OperationType с типами (CadPointers, Keyboards) и тип OfficePCType с типами (OfficePointers, Keyboards) и, наконец, для работы CAD-PC и Office-PC через все соединения, вы сможете обратитесь к правильному оборудованию. – MikeD

0

Из того, что я понял, вы хотите сохранить оборудование в отношении операций, таким образом, что позволит вам применить бизнес-логику к нему позже, в этом случае вы будете нуждаться в 3 таблицы:

операций :

  • ID
  • имя

оборудование:

  • ID
  • имя

Operations_Equipment:

  • equipment_id
  • operation_id
  • символ

где символ является А, В, С, и т.д. ...

Если у вас есть такое состояние, как (A & B) | (C & D), вы можете узнать, какое оборудование легко.

0

Be Aware Я не тестировал это в дикой природе. Это, как говорится, лучший способ, который я могу видеть, чтобы сделать сопоставление с денормализованной таблицей для группировки.

* (кроме пути Билла, который трудно установить, но властный, когда все сделано правильно)

Operations: 
-------------------- 
Op_ID int not null pk 
Op_Name varchar 500 

Equipment: 
-------------------- 
Eq_ID int not null pk 
Eq_Name varchar 500 
Total_Available int 

Group: 
-------------------- 
Group_ID int not null pk 
-- Here you have a choice. You can either: 
-- Not recommended 
Equip varchar(500) --Stores a list of EQ_ID's {1, 3, 15} 
-- Recommended 
Eq_ID_1 bit 
Eq_1_Total_Required 
Eq_ID_2 bit 
Eq_2_Total_Required 
Eq_ID_3 bit 
Eq_3_Total_Required 
-- ... etc. 

Operations_to_Group_Mapping: 
-------------------- 
Group_ID int not null frk 
Op_ID int not null frk 

Таким образом, в случае X: A | (D & E & F)

Operations: 
-------------------- 
Op_ID Op_Name 
1  X 

Equipment: 
-------------------- 
Eq_ID Eq_Name Total_Available 
1  A   5 
-- ... snip ... 
22  D   15 
23  E   0 
24  F   2 

Group: 
-------------------- 
Group_ID Eq_ID_1 Eq_1_Total_Required -- ... etc. ... 
1   TRUE  3 
-- ... snip ... 
2   FALSE  0 

Operations_to_Group_Mapping: 
-------------------- 
Group_ID Op_ID 
1   1 
2   1 
0

Как ненавидят, как я поставить рекурсивных (древовидных) структур в SQL, похоже, что это действительно то, что вы ищете. Я хотел бы использовать что-то смоделированный так:

Operation 
---------------- 
OperationID   PK 
RootEquipmentGroupID FK -> EquipmentGroup.EquipmentGroupID 
... 

Equipment 
---------------- 
EquipmentID   PK 
... 

EquipmentGroup 
---------------- 
EquipmentGroupID  PK 
LogicalOperator 

EquipmentGroupEquipment 
---------------- 
EquipmentGroupID |  (also FK -> EquipmentGroup.EquipmentGroupID) 
EntityType  |  PK (all 3 columns) 
EntityID   |  (not FK, but references either Equipment.EquipmentID 
         or EquipmentGroup.EquipmentGroupID) 

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

Каждая группа оборудования может быть либо and группой или группа or (обозначается столбцом LogicalOperator). Элементы каждой группы определены в таблице EquipmentGroupEquipment, где EntityID ссылается либо на Equipment.EquipmentID, либо на другой EquipmentGroup.EquipmentGroupID, причем цель определяется значением в EntityType. Это позволит вам составить группу, состоящую из оборудования или других групп.

Это позволит вам представить что-то же просто, как «требует оборудования A», который будет выглядеть следующим образом:

EquipmentGroupID LogicalOperator 
-------------------------------------------- 
1     'AND' 

EquipmentGroupID EntityType EntityID 
-------------------------------------------- 
1     1   'A' 

...все пути к вашему «A | D (E & & F)», который будет выглядеть следующим образом:

EquipmentGroupID LogicalOperator 
-------------------------------------------- 
1     'OR' 
2     'AND' 

EquipmentGroupID EntityType EntityID 
-------------------------------------------- 
1     1   'A' 
1     2   2 -- group ID 2 
2     1   'D' 
2     1   'E' 
2     1   'F' 

(я понимаю, что я имею смешанные типы данных в EntityID колонки, это просто сделать это ясно. Очевидно, что вы не сделали бы этого в реальной реализации)

Это также позволило бы представлять структуры произвольной сложности. Хотя я понимаю, что вы (правильно) не хотите переархитекровать решение, я не думаю, что вы действительно можете уйти с меньшими затратами, не нарушая 1NF (путем объединения нескольких устройств в один столбец).

Смежные вопросы