Внешний ключ c несколькими столбцами из разных таблиц

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

TABLE ANIMALS (
  NAME char,
  ANIMAL_TYPE char {'DOG', 'CAT'}
  PRIMARY KEY(NAME)
)

(например, у меня есть кот по имени Феликс и собака по имени Плуто)

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

TABLE PREFERED_FOOD (
  ANIMAL_NAME char,
  PREF_FOOD char
  FOREIGN KEY (ANIMAL_NAME) REFERENCES ANIMALS(NAME)
)

(например, Феликс любит молоко, а Плутон любит кости)

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

TABLE FOOD (
  ANIMAL_TYPE char {'DOG', 'CAT'},
  FOOD_TYPE char
)

(например, собаки едят кости и мясо, кошки едят рыбу и молоко)

вот мой вопрос: я хотел бы добавить иностранное ограничение в PREFERED_FOOD, так как PREF_FOOD-это FOOD_TYPE из FOOD with FOOD.ANIMAL_TYPE=ЖИВОТНЫЕ.ТИП. Как определить этот внешний ключ без дублирования ANIMAL_TYPE в PREFERED_FOOD ?

Я не эксперт с SQL, поэтому вы можете назвать меня глупым, если это действительно легко ; -)

5 ответов


вы не можете в SQL. Я думаю, ты может Если SQL поддерживает утверждения. (Стандартные утверждения SQL-92. Насколько мне известно, их пока никто не поддерживает.)

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

-- Nothing special here.
create table animal_types (
  animal_type varchar(15) primary key
);

create table animals (
  name varchar(15) primary key,
  animal_type varchar(15) not null references animal_types (animal_type),
  -- This constraint lets us work around SQL's lack of assertions in this case.
  unique (name, animal_type)
);

-- Nothing special here.
create table animal_food_types (
  animal_type varchar(15) not null references animal_types (animal_type),
  food_type varchar(15) not null,
  primary key (animal_type, food_type)
);

-- Overlapping foreign key constraints.
create table animals_preferred_food (
  animal_name varchar(15) not null,
  -- This column is necessary to implement your requirement. 
  animal_type varchar(15) not null,
  pref_food varchar(10) not null,
  primary key (animal_name, pref_food),
  -- This foreign key constraint requires a unique constraint on these
  -- two columns in "animals".
  foreign key (animal_name, animal_type) 
    references animals (animal_name, animal_type),
  -- Since the animal_type column is now in this table, this constraint
  -- is simple.
  foreign key (animal_type, pref_food) 
    references animal_food_types (animal_type, food_type)
);

FOREIGN KEY (PREF_FOOD) REFERENCES FOOD (FOOD_TYPE)

в таблице PREFERRED_FOOD это гарантирует, что каждый PREFFOOD в таблице PREFERRED_FOOD уже присутствует в FOOD_TYPE таблицы FOOD.

и в пользе таблицы еды, Свое довольно само-объяснительное теперь.

FOREIGN KEY (ANIMAL_TYPE) REFERENCES ANIMALS (ANIMAL_TYPE)

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

что-то вроде этого:

ALTER TABLE PREFERED_FOOD
ADD CONSTRAINT uc_FoodAnimal UNIQUE (ANIMAL_TYPE,PREFERED_FOOD)

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

enter image description here

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

с индивидуального.SPECIES_NAME-это FK в направлении и вид и SPECIES_FOOD, индивид никогда не может предпочесть пища, которая не съедобна по своему виду.

Это, конечно, предполагает, что отдельное животное не может иметь более одной предпочтительной пищи.1 он также предполагает, что он не может иметь ничего - если это не так, просто сделайте человека.PREFERRED_FOOD_NAME НЕ NULL.

индивидуальное имя было намеренно не сделал ключ, так что вы можете иметь, скажем, две кошки с именем "Феликс". Если это нежелательно, вы легко добавите соответствующее ключ.

Если все, что вам нужно знать о пище, - это ее название, и вам не нужно представлять пищу независимо от какого-либо вида, таблицу еды можно вообще опустить.


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


Если вы берете (естественное) соединение животных и PREFERRED_FOOD, то вы получаете таблицу, в которой для каждого животного перечислены его тип и предпочтительная еда.

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

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

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

CREATE ASSERTION <name for your constraint here>
 CHECK NOT EXISTS (SELECT ANIMAL_TYPE, FOOD_TYPE
                     FROM ANIMALS NATURAL JOIN PREF_FOOD
                    WHERE (ANIMAL_TYPE, FOOD_TYPE) NOT IN
                          SELECT ANIMAL_TYPE, FOOD_TYPE FROM FOOD_TYPE);

но ваш средний SQL engine не будет поддерживать утверждения. Поэтому вы должны использовать ограничения CHECK. Для таблицы PREF_FOOD, например, проверка ограничение, которое вам нужно, может выглядеть примерно так:

CHECK EXISTS (SELECT 1
                FROM FOOD NATURAL JOIN ANIMAL
               WHERE ANIMAL_TYPE = <animal type of inserted row> AND
                     FOOD_TYPE = <food type of inserted row>);

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

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

во всех этих трех сценариях вы предпочтительно захотите документировать существование ограничения и то, о чем именно идет речь, в каком-то другом месте. Ни один из трех не сделает это очевидным для третья сторона, читающая этот дизайн, Что, черт возьми, все это значит.

(*) "комплекс" может быть не совсем правильным словом, но обратите внимание, что такие решения полагаются на заведомая избыточность, таким образом намеренно идет ниже 3NF с дизайном. И это означает, что ваш дизайн подвержен аномалиям обновления, что означает, что пользователю будет сложнее обновлять базу данных и поддерживать ее согласованность (именно из-за преднамеренных избыточностей).