¿Restricción de clave externa en el miembro de la matriz?

27

Supongamos que tengo una tabla que contiene roles de trabajo:

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

Supongamos que además tengo una tabla, usuarios y cada fila (un usuario específico) puede tener un número arbitrario de roles de trabajo:

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

Probablemente debería asegurarme de que cada miembro de users.roles[]existe en roles.role. Me parece que lo que quiero es una restricción de clave externa en cada miembro de users.roles[]tal que si hace referencia a roles.role.

Esto no parece posible con postgres. ¿Estoy mirando esto de la manera incorrecta? ¿Cuál es la forma "correcta" sugerida para manejar esto?

usuario2965107
fuente

Respuestas:

20

Se trabajó en el soporte para claves externas de matriz con el objetivo de ponerlo en PostgreSQL 9.3, pero no logró el corte para la versión debido a problemas de rendimiento y confiabilidad. Parece que no se está trabajando en 9.4.

En este momento, debe apegarse al enfoque relacional habitual de usar una "tabla de unión" para modelar una relación m: n.

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

Sugiero usar también las claves sustitutas en este caso, en lugar de almacenar los nombres de usuario / roles directamente en la tabla de unión. La primera vez que desee cambiar el nombre de un usuario o rol, se alegrará de haber utilizado claves sustitutas. Simplemente coloque una uniquerestricción en roles."role"y users.username.

Craig Ringer
fuente
3

Acabo de hacer algo similar para un colega. Esencialmente hice una tabla oculta que contenía una fila para cada par (usuario, rol) con restricciones adecuadas. La tabla de usuario era entonces una vista de la tabla oculta con todos los roles reunidos en una matriz. Luego hice posible insertar en la vista agregando una regla apropiada. Aquí es cómo:

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

Espero que eso ayude. Puede hacerlo un poco más eficiente y agregar más reglas según sus requisitos.

Max Murphy
fuente
1

Una vez que obtenga el parche que permite esa funcionalidad más aquí

Solo usa: ELEMENT REFERENCES relation( field )

Por 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
fuente
1
Parece que gran idea, pero no se puede utilizar sin parchear manualmente el motor - esta es la razón de abajo-votos ...
langpavel