Ограничение внешнего ключа на элементе массива?

27

Предположим, у меня есть таблица, содержащая рабочие должности:

CREATE TABLE roles
(
  "role" character varying(80) NOT NULL,
  CONSTRAINT "role" PRIMARY KEY (role)
);

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

CREATE TABLE users
(
  username character varying(12) NOT NULL,
  roles character varying(80)[] NOT NULL,
  CONSTRAINT username PRIMARY KEY (username)
);

Я, наверное, должен убедиться, что каждый член users.roles[]существует в role.role. Мне кажется, что мне нужно ограничение внешнего ключа для каждого члена users.roles[], если оно ссылается на role.role.

Это не представляется возможным с Postgres. Я смотрю на это неправильно? Каков «правильный» способ справиться с этим?

user2965107
источник

Ответы:

20

Поддержка внешних ключей массива работала с целью включить ее в PostgreSQL 9.3, но она не попала в релиз из-за проблем с производительностью и надежностью. Кажется, он не работает над 9.4.

В настоящее время вам необходимо придерживаться обычного реляционного подхода с использованием «таблицы соединений» для моделирования отношения m: n.

CREATE TABLE user_roles (
   username character varying(12) references users(username),
   "role" character varying(80) references roles("role"),
   PRIMARY KEY(username, "role")
);

В этом случае я также предлагаю использовать суррогатные ключи , а не хранить имена пользователей / ролей непосредственно в таблице соединений. Когда вы в первый раз захотите переименовать пользователя или роль, вы будете рады, что использовали суррогатные ключи. Просто наложите uniqueограничение на roles."role"и users.username.

Крейг Рингер
источник
3

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

trailer=# create table harvester (id int unique, label text);
CREATE TABLE
trailer=# insert into harvester values (1,'grain'), (2,'cricket');
INSERT 0 2
trailer=# create table donkey (id int, others int references
harvester(id));
CREATE TABLE
trailer=# create unique index donkey_ears on donkey (id, others);
CREATE INDEX
trailer=# create view combine as select id, array_agg(others) as others
from donkey group by id;
CREATE VIEW
trailer=# create rule combine_insert as on insert to combine do instead
(delete from donkey where donkey.id=new.id;insert into donkey select
new.id,unnest(new.others) );
CREATE RULE
trailer=# insert into combine values (1,'{1,2}');INSERT 0 2
trailer=# select * from combine ;
id | others 
----+--------
  1 | {1,2}
(1 row)

trailer=# insert into combine values (1,'{1,2}');
INSERT 0 2
trailer=# select * from combine ;
 id | others 
----+--------
  1 | {1,2}
    (1 row)

trailer=# insert into combine values (2,'{1,2,3}');
ERROR:  insert or update on table "donkey" violates foreign key
constraint "donkey_others_fkey"
DETAIL:  Key (others)=(3) is not present in table "harvester".
trailer=# 

Надеюсь, это поможет. Вы можете сделать это немного более эффективным и добавить больше правил в зависимости от ваших требований.

Макс Мерфи
источник
1

Как только вы получите патч, который позволяет эту функциональность больше здесь

Просто используйте: ELEMENT REFERENCES relation( field )

Для примера:

CREATE TABLE drivers (
   driver_id integer PRIMARY KEY,
   first_name text,
   last_name text,
   ...
);



CREATE TABLE races (
   race_id integer PRIMARY KEY,
   title text,
   race_day DATE,
   ...
   practice1_positions integer[] ELEMENT REFERENCES drivers,
   practice2_positions integer[] ELEMENT REFERENCES drivers,
   practice3_positions integer[] ELEMENT REFERENCES drivers,
   qualifying_positions integer[] ELEMENT REFERENCES drivers,
   final_positions integer[] ELEMENT REFERENCES drivers
);
Ян Мбаэ
источник
1
Это выглядит как отличная идея, но ее нельзя использовать без ручного исправления движка - это причина
отрицательных