Каков наилучший способ обеспечить соблюдение отношения «подмножество» с ограничениями целостности - PullRequest
4 голосов
/ 02 апреля 2011

Например, учитывая 3 таблицы:

  • брюхоногий
  • улитка
  • слизняк

и предполагая, что мы хотим применить это

  1. каждая строка в 'gastropod' имеет ровно одну соответствующую строку в 'улитке' или 'слизняке' (но не в обоих)
  2. каждая строка в 'слаге' имеет ровно одну соответствующую строкув 'gastropod'
  3. каждая строка в 'улитке' имеет ровно одну соответствующую строку в 'gastropod'

Каков наилучший способ настроить мою схему для применения этих ограничений?

Я предоставил один возможный ответ для postgres, и мне особенно интересны решения для postgres и Oracle, но также было бы интересно увидеть решения для других СУБД

EDIT
Для справки, ТАК вопросы из ответов / комментариев ниже, касающихся аналогичных проблем:

Ответы [ 12 ]

3 голосов
/ 02 апреля 2011

Мое собственное решение для postgres (но я не знаю, лучший ли это способ):

перечисление:

create type gastropod_type as enum ('slug', 'snail');

таблицы и ограничения:

create table gastropod(
  gastropod_id serial unique,
  gastropod_type gastropod_type,
  slug_gastropod_id integer,
  snail_gastropod_id integer,
  average_length numeric,
  primary key(gastropod_id, gastropod_type),
  check( (case when slug_gastropod_id is null then 0 else 1 end)+
         (case when snail_gastropod_id is null then 0 else 1 end)=1) );

create table slug(
  gastropod_id integer unique,
  gastropod_type gastropod_type check (gastropod_type='slug'),
  is_mantle_visible boolean,
  primary key(gastropod_id, gastropod_type),
  foreign key(gastropod_id, gastropod_type) 
    references gastropod deferrable initially deferred );

create table snail(
  gastropod_id integer unique,
  gastropod_type gastropod_type check (gastropod_type='snail'),
  average_shell_volume numeric,
  primary key(gastropod_id, gastropod_type),
  foreign key(gastropod_id, gastropod_type)
    references gastropod deferrable initially deferred );

alter table gastropod 
add foreign key(slug_gastropod_id, gastropod_type) 
references slug deferrable initially deferred;

alter table gastropod 
add foreign key(snail_gastropod_id, gastropod_type) 
references snail deferrable initially deferred;

тест:

insert into gastropod(gastropod_type, slug_gastropod_id, average_length)
values ('slug', currval('gastropod_gastropod_id_seq'), 100);

insert into slug(gastropod_id, gastropod_type, is_mantle_visible)
values (currval('gastropod_gastropod_id_seq'), 'slug', true);

select gastropod_id, gastropod_type, average_length, is_mantle_visible
from gastropod left outer join slug using(gastropod_id, gastropod_type) 
               left outer join snail using(gastropod_id, gastropod_type);

 gastropod_id | gastropod_type | average_length | is_mantle_visible
--------------+----------------+----------------+-------------------
            1 | slug           |            100 | t                 
(1 row)
1 голос
/ 03 апреля 2011

Я бы пошел с

DROP TABLE GASTROPOD PURGE;
DROP TABLE SNAIL PURGE;

CREATE TABLE GASTROPOD
  (GASTROPOD_ID NUMBER,
  GASTROPOD_TYPE VARCHAR2(5),
  SNAIL_ID NUMBER,
  SLUG_ID NUMBER,
  CONSTRAINT GASTROPOD_PK PRIMARY KEY (GASTROPOD_ID),
  CONSTRAINT GASTROPOD_TYPE_CK CHECK (GASTROPOD_TYPE IN ('SLUG','SNAIL')),
  CONSTRAINT GASTROPOD_SLUG_CK CHECK 
     (SNAIL_ID IS NOT NULL OR SLUG_ID IS NOT NULL),
  CONSTRAINT GASTROPOD_SNAIL_CK1 CHECK 
     (GASTROPOD_TYPE = 'SNAIL' OR SLUG_ID IS NULL),
  CONSTRAINT GASTROPOD_SLUG_CK1 CHECK 
     (GASTROPOD_TYPE = 'SLUG' OR SNAIL_ID IS NULL),
  CONSTRAINT GASTROPOD_SNAIL_CK2 CHECK (SNAIL_ID = GASTROPOD_ID),
  CONSTRAINT GASTROPOD_SLUG_CK2 CHECK (SLUG_ID = GASTROPOD_ID),
  CONSTRAINT GASTROPOD_SNAIL_UK UNIQUE (SNAIL_ID),
  CONSTRAINT GASTROPOD_SLUG_UK UNIQUE (SLUG_ID)
  );

Таким образом, вы проверяете, что брюхоногий моллюск - это улитка или слизняк, и установлен либо slug_id, либо snail_id.Если это улитка, то slug_id должен быть нулевым, а для слаггена snail_id должен быть нулевым.Убедитесь, что идентификаторы слизней и улиток уникальны (я добавил проверки, чтобы сопоставить их и с gastropod_id).

CREATE TABLE SNAIL
  (SNAIL_ID NUMBER,
   CONSTRAINT SNAIL_PK PRIMARY KEY (SNAIL_ID),
   CONSTRAINT SNAIL_FK FOREIGN KEY (SNAIL_ID)
     REFERENCES GASTROPOD (SNAIL_ID));

Улитки должны указывать на строку в gastropod, где snail_id не равно нулю, и этопервичный ключ (и, следовательно, уникальный)

ALTER TABLE GASTROPOD ADD CONSTRAINT SNAIL_GS_FK FOREIGN KEY (SNAIL_ID)
     REFERENCES SNAIL (SNAIL_ID) DEFERRABLE INITIALLY DEFERRED;

Гастроподы с набором snail_id также должны иметь соответствующую строку в улитке.Я сделал это направление отсроченным, иначе вы никогда не получите никаких новых данных.

1 голос
/ 02 апреля 2011

"и при условии, что мы хотим применить это (1) каждая строка в 'gastropod' имеет ровно одну соответствующую строку в 'улитке' или 'слизняке' (но не оба) (2) каждая строка в 'slug' имеет ровно одну соответствующую строку в 'gastropod' (3) каждая строка в «улитке» имеет ровно одну соответствующую строку в «гастроподе» "

(1) - это зависимость включения (она называется «зависимость от внешнего ключа») между GASTROPOD и виртуальным relvar (она называется «представление»), определяемая как SLUG UNION SNAIL. (2) и (3) являются одинаковыми типами зависимостей включения между «SLUG» (/ «SNAIL») и «GASTROPOD».

Все вместе взятые означают, что у вас есть «зависимость равенства» между «GASTROPOD» и «SLNA UNION SNAIL» (по крайней мере, в отношении идентификаторов).

Обратите внимание, что для обновления базы данных, на которую распространяются такие ограничения, вам, скорее всего, потребуется либо механизм СУБД, поддерживающий эту функцию, называемую «множественное назначение», либо другой, поддерживающий «проверку отложенных ограничений».

Глава 11 книги «Прикладная математика для специалистов по базам данных» углубленно рассматривает вопрос о том, как обеспечить выполнение таких ограничений (и фактически любых ограничений, даже сложных) в средах SQL. Ответ на ваш вопрос - почти все содержание этой главы, и я надеюсь, что вы не ожидаете, что я кратко изложу здесь все это в нескольких словах (суть ответа - «триггеры» - как также указывал StarShip3000).

1 голос
/ 02 апреля 2011

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

1 голос
/ 02 апреля 2011

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

1 голос
/ 02 апреля 2011

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

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

Есть некоторые обходные пути, но ни одна из них не является идеальной.Вы можете использовать ограничения DEFERRABLE, если они есть в вашей СУБД (например, в Oracle).Ограничение DEFERRABLE на самом деле является простым способом отключения ограничения.Или вы можете использовать триггеры, что означает, что правило применяется процедурно, а не через соответствующее ограничение базы данных.

1 голос
/ 02 апреля 2011

Проверить эту тему: Поддержание целостности подкласса в реляционной базе данных

Поток предоставляет несколько предложений для реализаций SQL Server, и я был бы удивлен, если бы идеи не могли быть применены и к Oracle.

0 голосов
/ 10 июля 2017

В идеале, я бы сделал одну таблицу «gastropod» с полем «type», а затем имел бы представления «gastropod» (выбирая все поля, кроме «type», без условия «where»), «snail» (используяусловие «где» для ограничения типа slug) и «slug» (с помощью предложения «где» для ограничения типа slug).Исключения могут существовать, если один из двух типов намного меньше, и есть много полей, относящихся только к меньшему типу, но по большей части, делая его различными представлениями из одной таблицы, обеспечит надлежащие ограничения целостности.

0 голосов
/ 21 декабря 2012

У вас есть две проблемы:

  • Присутствие : не может быть родительской строки без хотя бы одной дочерней строки.
  • Эксклюзивность : не может быть родительской строки с более чем одной дочерней строкой.

В СУБД, поддерживающей отложенные ограничения (включая PostgreSQL и Oracle), обе эти цели могут быть достигнуты декларативно:

enter image description here

Круглый внешний ключ находится между gastropod.snail_id и snail.snail_id, а также между gastropod.slug_id и slug.slug_id.Существует также CHECK, который гарантирует, что точно один из них соответствует gastropod.gastropod_id (а другой равен NULL).

Чтобы устранить проблему "курица и яйцо" при вставке новых данных, отложитеодно направление внешних ключей.

Вот как это будет реализовано в PostgreSQL:

CREATE TABLE gastropod (
    gastropod_id int PRIMARY KEY,
    snail_id int UNIQUE,
    slug_id int UNIQUE,
    CHECK (
        (slug_id IS NULL AND snail_id IS NOT NULL AND snail_id = gastropod_id)
        OR (snail_id IS NULL AND slug_id IS NOT NULL AND slug_id = gastropod_id)
    )    
);

CREATE TABLE snail (
    snail_id int PRIMARY KEY,
    FOREIGN KEY (snail_id) REFERENCES gastropod (snail_id) ON DELETE CASCADE
);

CREATE TABLE slug (
    slug_id int PRIMARY KEY,
    FOREIGN KEY (slug_id) REFERENCES gastropod (slug_id) ON DELETE CASCADE
); 

ALTER TABLE gastropod ADD FOREIGN KEY (snail_id) REFERENCES snail (snail_id)
    DEFERRABLE INITIALLY DEFERRED;

ALTER TABLE gastropod ADD FOREIGN KEY (slug_id) REFERENCES slug (slug_id)
    DEFERRABLE INITIALLY DEFERRED;

Новые данные вставляются следующим образом:

START TRANSACTION;
INSERT INTO gastropod (gastropod_id, snail_id) VALUES (1, 1);
INSERT INTO snail (snail_id) VALUES (1);
COMMIT;

Однако при попыткене удается вставить только родительский, но не дочерний:

START TRANSACTION;
INSERT INTO gastropod (gastropod_id, snail_id) VALUES (2, 2);
COMMIT; -- FK violation.

Не удается вставить неправильный тип дочернего элемента:

START TRANSACTION;
INSERT INTO gastropod (gastropod_id, snail_id) VALUES (2, 2);
INSERT INTO slug (slug_id) VALUES (2); -- FK violation.
COMMIT;

И вставить слишком малое, слишком много или несоответствующие поля в родительскомтакже происходит сбой:

INSERT INTO gastropod (gastropod_id) VALUES (2); -- CHECK violation.
...
INSERT INTO gastropod (gastropod_id, snail_id, slug_id) VALUES (2, 2, 2); -- CHECK violation.
...
INSERT INTO gastropod (gastropod_id, snail_id) VALUES (1, 2); -- CHECK violation.

В СУБД, которая не поддерживает отложенные ограничения, эксклюзивность (но не наличие) может быть принудительно применена следующим образом:

enter image description here

В СУБД, которая поддерживает вычисляемые поля (например, виртуальные столбцы Oracle 11), дискриминатор типа type не должен физически храниться на уровне дочерних таблиц (только родительская таблица).

уникальный констraint U1 может понадобиться в СУБД, которые не поддерживают FK-ссылки супер-набор ключа (насколько я знаю, почти все из них), поэтому мы создаем этот супер-набор искусственно.


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

0 голосов
/ 03 апреля 2011

Все эти примеры имеют ужасный уровень сложности для чего-то такого простого:

create table gastropod(
    average_length numeric
);
create table slug(
    like gastropod,
    id          serial  primary key,
    is_mantle_visible boolean
);
create table snail(
    like gastropod,
    id          serial  primary key,
    average_shell_volume numeric
);   
\d snail;

        Column        |  Type   |                     Modifiers                      
----------------------+---------+----------------------------------------------------
 average_length       | numeric | 
 id                   | integer | not null default nextval('snail_id_seq'::regclass)
 average_shell_volume | numeric | 
Indexes:
    "snail_pkey" PRIMARY KEY, btree (id)

Прежде чем сказать, что это не ответ, подумайте о требованиях.

  1. каждыйстрока в 'gastropod' имеет ровно одну соответствующую строку в 'улитке' или 'slug' (но не в обоих)
  2. каждая строка в 'slug' имеет ровно одну соответствующую строку в 'gastropod'
  3. каждая строка в 'улитке' имеет ровно одну соответствующую строку в 'gastropod'

Наличие столбца в таблице является эквивалентом целостности данных без всякой ерунды.

Примечание: LIKE в DDL может скопировать все столбцы (даже ограничения и индексы в 9.0) в новую таблицу.Таким образом, вы можете что-то вроде поддельного наследства.

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