Моделирование одноконстантных отношений - PullRequest
4 голосов
/ 06 августа 2009

Может ли отношение «один к константе» быть полностью смоделировано в Oracle с ограничениями? Другими словами, сущность PARENT ВСЕГДА имеет ТОЧНО n-ДЕТЕЙ дочерней сущности, и у каждого ребенка только один родитель.

Рассмотрим n как константу базы данных.

Ответы [ 7 ]

5 голосов
/ 06 августа 2009

Сделать это так, чтобы оно было правильным и корректным, даже если несколько сеансов выполняют обновления, непросто. Вы попадете в беспорядок, если попробуете это с помощью триггеров, а декларативные ограничения Oracle недостаточно сильны, чтобы выразить это.

Это можно сделать следующим образом: -

  1. Создание материализованного журнала представлений как для родительской, так и для дочерней таблиц
  2. Создать материализованное представление объединения, которое объединяет их и подсчитывает количество дочерних элементов, сгруппированных по родителю. Это должно быть REFRESH FAST ON COMMIT
  3. Ограничить представление материализованного объединения тем, что количество дочерних записей должно равняться "n" (константе вашей базы данных)

Затем вы можете выполнить серию операторов вставки / обновления / удаления. Когда вы фиксируете, материализованное представление обновляется, и если условие не выполняется, вы получите ошибку нарушения ограничения в этой точке.

Дополнительный хитрость заключается в том, что в материализованное представление включаются только строки, которые не соответствуют ограничению (счетчик HAVING (ChildId) <> 5), поэтому вы не тратите место для хранения.

3 голосов
/ 06 августа 2009

Опираясь на более ранние точки "курица + яйцо", вы можете создавать отложенные ограничения, которые не проверяются до момента фиксации ... это может помочь?

, например

ALTER TABLE AGREEMENTS ADD CONSTRAINT name FOREIGN KEY (column) REFERENCES table (column) DEFERRABLE INITIALLY DEFERRED;
1 голос
/ 07 августа 2009

Существует альтернативное решение заставить каждого родителя иметь ровно 0 или n дочерних элементов без материализованных представлений, используя только проверку, внешний ключ и ограничения уникальности. Для этого нужно нумеровать детей и добавить поле, содержащее номер следующего брата. Вот пример для n = 5, который работает в PostgreSQL, для других DBS нужно адаптировать, вероятно, тип serial :

create table Tree(
  id serial,
  parent_id integer not null references Tree(id),
  child_nr integer check(child_nr between 1 and 5),
  next_sibling_nr integer,
  unique (parent_id, child_nr),
  check(next_sibling_nr in (child_nr+1, child_nr-4)),
  check(((parent_id is null) and (child_nr is null) and
    (next_sibling_nr is null)) or ((parent_id is not null)
    and (child_nr is not null) and (next_sibling_nr is not null))),
  foreign key (parent_id, next_sibling_nr) references Tree(parent_id, child_nr),
  primary key (id)
);

Последнее (длинное) проверочное ограничение гарантирует, что поля parent_id, child_nr и next_sibling_nr все имеют значение NULL или не все NULL. Ограничение уникальности и проверка поля child_nr позаботятся о том, чтобы родитель имел максимум 5 детей. Другое проверочное ограничение и ограничение внешнего ключа для пары (parent_id, next_sibling_nr) обеспечивают наличие не менее 5 дочерних элементов.

После вставки рута с автоматически сгенерированным идентификатором 1 командой

insert into Tree (parent_id)
  values (null);

можно добавлять детей всегда в упаковках по 5:

insert into Tree (parent_id, child_nr, next_sibling_nr)
  values (1, 1, 2),
         (1, 2, 3),
         (1, 3, 4),
         (1, 4, 5),
         (1, 5, 1);

Это решение получено из ответов на аналогичный вопрос, который я задавал несколько недель назад .

1 голос
/ 06 августа 2009

Не понимаю как. Это старый вопрос «который пришел первым: курица или яйцо?». Как вы можете ограничить родителя, когда еще не было добавлено ни одного ребенка, и как вы можете добавить детей без родителя?

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

0 голосов
/ 06 августа 2009

Вы можете создать свои таблицы как обычно с отношением 1: M, затем в дочерней таблице есть столбец count с проверочным ограничением, определяющим, сколько дочерних элементов может существовать для родителя, а также уникальным ограничением для Parent. ID + количество столбцов. e.g.:

CREATE TABLE Parent (PID NUMBER PRIMARY KEY);

CREATE TABLE Child (
    PID NUMBER NOT NULL,
    Count NUMBER(1) NOT NULL,
    CONSTRAINT count_check CHECK (Count BETWEEN 1 AND 5),
    CONSTRAINT parent_child_fk FOREIGN KEY (PID) REFERENCES Parent (PID),
    CONSTRAINT count_unique UNIQUE (PID, Count));

Единственное, что это не гарантирует, - это то, что на каждого родителя как минимум пять детей; чтобы обойти это, вы можете создать материализованное представление с ограничением, как предлагает WW, или создать что-то дополнительное в приложении (например, отчет об ошибке).

0 голосов
/ 06 августа 2009

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

В этом примере n = 3.

SQL> create table parent
  2   ( pk_col number not null
  3     , col1 varchar2(20)
  4     , constraint par_pk primary key (pk_col)
  5    )
  6  /

Table created.

SQL> 
SQL> create table child
  2   ( pk_col number not null
  3     , seqno number(1,0) not null
  4     , col2 date
  5     , constraint ch_pk primary key
  6          (pk_col, seqno)
  7     , constraint ch_par_fk foreign key
  8          (pk_col) references parent (pk_col)
  9     , constraint ch_ck check (seqno between 1 and 3)
 10    )
 11  /

Table created.

SQL> 
SQL> create table child_lock
  2   ( pk_col_1 number not null
  3     , seqno_1 number(1,0) not null
  4     , pk_col_2 number not null
  5     , seqno_2 number(1,0) not null
  6     , pk_col_3 number not null
  7     , seqno_3 number(1,0) not null
  8     , constraint chlk_pk primary key
  9          (pk_col_1, seqno_1, pk_col_2, seqno_2, pk_col_3, seqno_3)
 10     , constraint chlk_par_1_fk foreign key
 11          (pk_col_1, seqno_1) references child (pk_col, seqno)
 12     , constraint chlk_par_2_fk foreign key
 13          (pk_col_2, seqno_2) references child (pk_col, seqno)
 14     , constraint chlk_par_3_fk foreign key
 15          (pk_col_3, seqno_3) references child (pk_col, seqno)
 16    )
 17  /

Table created.

SQL> 
SQL> insert all
  2      into parent values (pk_val, val_1)
  3      into child values (pk_val, 1, sysdate)
  4      into child values (pk_val, 2, sysdate+1)
  5      into child values (pk_val, 3, sysdate+2)
  6      into child_lock values (pk_val, 1, pk_val, 2, pk_val, 3)
  7  select 999 as pk_val
  8         , 'APPLE PIE' as val_1
  9  from dual
 10  /

5 rows created.

SQL> 
SQL> insert into child values (999, 4, sysdate+4)
  2  /
insert into child values (999, 4, sysdate+4)
*
ERROR at line 1:
ORA-02290: check constraint (APC.CH_CK) violated 


SQL> insert into child values (999, 3, sysdate+4)
  2  /
insert into child values (999, 3, sysdate+4)
*
ERROR at line 1:
ORA-00001: unique constraint (APC.CH_PK) violated 


SQL> insert into child values (999, 2.5, sysdate+4)
  2  /
insert into child values (999, 2.5, sysdate+4)
*
ERROR at line 1:
ORA-00001: unique constraint (APC.CH_PK) violated 


SQL> delete from child
  2  /
delete from child
*
ERROR at line 1:
ORA-02292: integrity constraint (APC.CHLK_PAR_1_FK) violated - child record found 


SQL>

Я принимаю, что решение немного придумано и также негибко, но то же самое относится и к первоначальному требованию. Это также далеко от пуленепробиваемого - удалите строку из CHILD_LOCK, и вы можете удалить одну или несколько записей CHILD.

0 голосов
/ 06 августа 2009

Это может быть не то, что вы хотите, но у меня есть один метод, который делает что-то похожее.

Обычное расположение один-ко-многим выглядит примерно так:

Primary Table:
primary_id (PK)
primary_stuff

Secondary Table:
secondary_id (PK)
primary_id (FK)
secondary_stuff

Альтернатива моделированию строгого индивидуального подхода:

Primary Table:
primary_id (PK)
secondary_id (FK, non-null)
primary_stuff

Secondary Table:
secondary_id (PK)
secondary_stuff

Это может быть немного странно, но это работает. Вариант этого может быть полезен, когда есть один-ко-многим, один-к-одному, например, наличие нескольких адресов для клиента, но ровно один платежный адрес.

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