Oracle - ссылочная целостность с несколькими типами данных - PullRequest
3 голосов
/ 11 января 2012

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

В частности, у меня есть несколько разных таблиц - гипотетически, скажем, у меня есть яблоки, бананы, апельсины, мандарины, виноград и еще сто видов фруктов. Сейчас я пытаюсь составить таблицу, которая описывает выполнение шагов с участием фруктов. Поэтому я хочу вставить одну строку с надписью «съесть Apple ID 100», затем еще одну строку с надписью «очистить банановый ID 250», затем еще одну строку с надписью «охладить Tangerine ID 500» и так далее.

Исторически мы делали это двумя способами:

1 - Включите столбец для каждого возможного типа фруктов. Используйте проверочное ограничение, чтобы убедиться, что все столбцы, кроме одного, имеют значение NULL. Используйте внешние ключи для обеспечения ссылочной целостности наших фруктов. Так что в моем гипотетическом примере у нас будет таблица со столбцами ACTION, APPLEID, BANANAID, ORANGEID, TANGERINEID и GRAPEID. Для первого действия у нас будет строка 'Eat', 100, NULL, NULL, NULL, NULL, NULL. Для второго действия у нас будет 'Peel', NULL, 250, NULL, NULL, NULL. и т. д.

Этот подход отлично подходит для автоматического получения всех преимуществ Oracle RI, но он не подходит для сотен видов фруктов. В итоге вы получаете слишком много столбцов, чтобы быть практичными. Просто выяснить, с каким фруктом вы имеете дело, становится проблемой.

2 - Включить столбец с названием фрукта и столбец с идентификатором фрукта. Это также работает, но не существует способа (AFAIK), чтобы Oracle каким-либо образом осуществлял проверку достоверности данных. Таким образом, наши столбцы будут ACTION, FRUITTYPE и FRUITID. Данные строки будут 'Eat', 'Apple', 100, затем 'Peel', 'Banana', 250 и т. Д. Но ничто не мешает кому-либо удалить Apple ID 100 или вставить шаг, говорящий 'Eat', 'Apple', 90000000, даже если у нас нет Apple с таким идентификатором.

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

РАЗЪЯСНЕНИЕ: В нашей реальной логике "плоды" - это совершенно разные таблицы с очень небольшой общностью. Подумайте о клиентах, сотрудниках, собраниях, комнатах, зданиях, тегах активов и т. Д. Предполагается, что список шагов должен быть в произвольной форме и позволять пользователям указывать действия для любой из этих вещей. Если бы у нас был один стол, в котором содержались все эти несвязанные вещи, у меня не было бы проблемы, но это также было бы действительно странным дизайном.

1 Ответ

6 голосов
/ 11 января 2012

Мне не понятно, зачем вам нужно указывать FRUIT_TYPE в таблице TASKS. На первый взгляд, это просто плохая (ненормализованная) модель данных.

По моему опыту, лучший способ моделирования данных такого типа - использовать супертип для универсального элемента (FRUIT в вашем примере) и подтипы для специфики (APPLE, GRAPE, BANANA). Это позволяет нам хранить общие атрибуты в одном месте при записи конкретных атрибутов для каждого экземпляра.

Вот таблица супертипов:

create table fruits
    (fruit_id number not null
         , fruit_type varchar2(10) not null
         , constraint fruit_pk primary key (fruit_id)
         , constraint fruit_uk unique (fruit_id, fruit_type)
         , constraint fruit_ck check (fruit_type in ('GRAPE', 'APPLE', 'BANANA'))
    )
/

FRUITS имеет первичный ключ и составной уникальный ключ. Нам нужен первичный ключ для использования в ограничениях внешнего ключа, потому что составные ключи - это боль в шее. За исключением случаев, когда это не так, что является ситуацией с этими таблицами подтипов. Здесь мы используем уникальный ключ в качестве ссылки, потому что, ограничивая значение FRUIT_TYPE в подтипе, мы можем гарантировать, что записи в таблице GRAPES отображаются в записи FRUITS типа 'GRAPE' и т. Д.

create table grapes
    (fruit_id number not null
         , fruit_type varchar2(10) not null  default 'GRAPE'
         , seedless_yn  not null char(1) default 'Y'
         , colour varchar2(5) not null
         , constraint grape_pk primary key (fruit_id)
         , constraint grape_ck check (fruit_type = 'GRAPE')
         , constraint grape_fruit_fk foreign key (fruit_id, fruit_type)
                references fruit  (fruit_id, fruit_type)
         , constraint grape_flg_ck check (seedless_yn in ('Y', 'N'))
    )
/

create table apples
    (fruit_id number not null
         , fruit_type varchar2(10) not null
         , apple_type  varchar2(10) not null default 'APPLE'
         , constraint apple_pk primary key (fruit_id)
         , constraint apple_ck check (fruit_type = 'APPLE')
         , constraint apple_fruit_fk foreign key (fruit_id, fruit_type)
                references fruit  (fruit_id, fruit_type)
         , constraint apple_type_ck check (apple_type in ('EATING', 'COOKING', 'CIDER'))
    )
/

create table bananas
    (fruit_id number not null
         , fruit_type varchar2(10) not null default 'BANANA'
         , constraint banana_pk primary key (fruit_id)
         , constraint banana_ck check (fruit_type = 'BANANA')
         , constraint banana_fruit_fk foreign key (fruit_id, fruit_type)
                references fruit  (fruit_id, fruit_type)
    )
/

В 11g мы можем сделать FRUIT_TYPE виртуальным столбцом для подтипа и устранить ограничение проверки.

Итак, теперь нам нужна таблица для типов задач («Пил», «Охлаждение», «Ешь» и т. Д.).

create table task_types
    (task_code varchar2(4) not null
     , task_descr varchar2(40) not null
     , constraint task_type_pk primary key (task_code)
    )
/

А настоящая таблица TASKS - это простое пересечение между FRUITS и TASK_TYPES.

create table tasks
    (task_code varchar2(4) not null
     , fruit_id number not null
     , constraint task_pk primary key (task_code, fruit_id)
     , constraint task_task_fk ask foreign key (task_code)
            references task_types (task_code)
     , constraint task_fruit_fk foreign key (fruit_id)
            references fruit (fruit_id)
/

Если это не удовлетворяет вашим потребностям, отредактируйте ваш вопрос, включив в него дополнительную информацию.


"... если вы хотите разные задачи для разных фруктов ..."

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


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

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

Я думаю, вам все еще нужна общая таблица для хранения идентификатора актера в задании, но вам нужно как-то связать его с другими таблицами. Вот как я мог бы подойти к этому:

Пример существующих таблиц:

create table customers
    (cust_id number not null
         , cname varchar2(100) not null
         , constraint cust_pk primary key (fruit_id)
    )
/

create table employees
    (emp_no number not null
         , ename varchar2(30) not null
         , constraint emp_pk primary key (fruit_id)
    )
/

Общая таблица для размещения актеров:

create table actors
    (actor_id number not null
         , constraint actor_pk primary key (actor_id)
    )
/

Теперь вам нужны таблицы пересечений, чтобы связать существующие таблицы с новой:

create table cust_actors
    (cust_id number not null
         , actor_id number not null
         , constraint cust_actor_pk primary key (cust_id, actor_id)
         , constraint cust_actor_cust_fk foreign key (cust_id)
                references customers (cust_id)
         , constraint cust_actor_actor_fk foreign key (actor_id)
                references actors (actor_id)
    )
/

create table emp_actors
    (emp_no number not null
         , actor_id number not null
         , constraint emp_actor_pk primary key (emp_no, actor_id)
         , constraint emp_actor_emp_fk foreign key (emp_no)
                references eployees (emp_no)
         , constraint cust_actor_actor_fk foreign key (actor_id)
                references actors (actor_id)
    )
/

Таблица ЗАДАЧ довольно неудивительна, учитывая то, что было раньше:

create table tasks
    (task_code varchar2(4) not null
     , actor_id number not null
     , constraint task_pk primary key (task_code, actor_id)
     , constraint task_task_fk ask foreign key (task_code)
            references task_types (task_code)
     , constraint task_actor_fk foreign key (actor_id)
            references actors (actor_id)
/

Я согласен, что все эти таблицы пересечений выглядят как большие накладные расходы, но нет другого способа применить ограничения внешнего ключа. Дополнительным препятствием является создание записей ACTORS и CUSTOMER_ACTORS каждый раз, когда вы создаете запись в CUSTOMERS. То же самое для удалений. Единственная хорошая новость заключается в том, что вы можете сгенерировать весь необходимый код.

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

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