Contrainte de clé étrangère sur un membre du tableau?

27

Supposons que j'ai une table contenant des rôles de travail:

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

Supposons que j'ai en outre une table, des utilisateurs et que chaque ligne (un utilisateur spécifique) puisse avoir un nombre arbitraire de rôles de travail:

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

Je devrais probablement m'assurer que chaque membre de users.roles[]existe dans roles.role. Il me semble que ce que je veux, c'est une contrainte de clé étrangère sur chaque membre de users.roles[]telle que if fait référence à roles.role.

Cela ne semble pas possible avec les postgres. Suis-je en train de regarder ça dans le mauvais sens? Quelle est la "bonne" façon de gérer cela?

user2965107
la source

Réponses:

20

La prise en charge des clés étrangères de tableau a été travaillée dans le but de l'intégrer dans PostgreSQL 9.3, mais elle n'a pas été supprimée en raison de problèmes de performances et de fiabilité. Il ne semble pas être en cours d'élaboration pour 9.4.

À ce stade, vous devez vous en tenir à l'approche relationnelle habituelle consistant à utiliser une «table de jointure» pour modéliser une relation m: n.

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

Je suggère également d'utiliser des clés de substitution dans ce cas, plutôt que de stocker les noms d'utilisateur / noms de rôle directement dans la table de jointure. La première fois que vous souhaitez renommer un utilisateur ou un rôle, vous serez heureux d'avoir utilisé des clés de substitution. Placez simplement une uniquecontrainte sur roles."role"et users.username.

Craig Ringer
la source
3

Je viens de faire quelque chose de similaire pour un collègue. Essentiellement, j'ai créé une table cachée qui contenait une ligne pour chaque paire (utilisateur, rôle) avec des contraintes appropriées. La table utilisateur était alors une vue de la table cachée avec tous les rôles assemblés dans un tableau. J'ai ensuite rendu possible l'insertion dans la vue en ajoutant une règle appropriée. Voici comment:

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=# 

J'espère que ça aide. Vous pouvez le rendre un peu plus efficace et ajouter plus de règles en fonction de vos besoins.

Max Murphy
la source
1

Une fois que vous obtenez le patch qui permet cette fonctionnalité plus ici

Utilisez simplement: ELEMENT REFERENCES relation( field )

Pour intance:

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
);
Ian Mbae
la source
1
Cela ressemble à une excellente idée, mais il ne peut pas être utilisé sans patcher manuellement le moteur - c'est la raison des votes
négatifs