Hacer cumplir las restricciones "a dos mesas de distancia"

10

Me encontré con algunos problemas al modelar un esquema eléctrico en SQL. La estructura que me gustaría capturar es

  part ←────────── pin
                   
part_inst ←───── pin_inst

donde "inst" es la abreviatura de "instancia".

Por ejemplo, podría tener como partamplificador operacional pinLM358 con s 1OUT, 1IN-, 1IN +, GND, 2IN +, 2IN-, 2OUT y V CC . Entonces podría colocar esta parte en un esquema, creando ay part_inst8 pin_insts.

Ignorando los campos de datos, mi intento inicial de un esquema fue

create table parts (
    part_id bigserial primary key
);
create table pins (
    pin_id bigserial primary key,
    part_id bigint not null references parts
);
create table part_insts (
    part_inst_id bigserial primary key,
    part_id bigint not null references parts
);
create table pin_insts (
    pin_inst_id bigserial primary key,
    part_inst_id bigint not null references part_insts,
    pin_id bigint not null references pins
);

El principal problema con este esquema es que a pin_instpodría estar vinculado a un part_instcon part_id=1pero pintiene part_id=2.

Me gustaría evitar este problema en el nivel de la base de datos en lugar del nivel de la aplicación. Entonces, modifiqué mis claves principales para hacer cumplir eso. Marqué las líneas cambiadas con --.

create table parts (
    part_id bigserial primary key
);
create table pins (
    pin_id bigserial,                                          --
    part_id bigint not null references parts,
    primary key (pin_id, part_id)                              --
);
create table part_insts (
    part_inst_id bigserial,                                    --
    part_id bigint not null references parts,
    primary key (part_inst_id, part_id)                        --
);
create table pin_insts (
    pin_inst_id bigserial primary key,
    part_inst_id bigint not null,                              --
    pin_id bigint not null,                                    --
    part_id bigint not null references parts,                  --
    foreign key (part_inst_id, part_id) references part_insts, --
    foreign key (pin_id, part_id) references pins              --
);

Mi queja con este método es que contamina las claves principales: en todas partes me refiero a part_inst, necesito hacer un seguimiento tanto de la part_inst_idcomo de la part_id. ¿Hay alguna otra manera de hacer cumplir la restricción pin_inst.part_inst.part_id = pin_inst.pin.part_idsin ser demasiado detallado?

Bola de nieve
fuente
También podría eliminar la pin_inst_idredundancia. Puede usar (part_inst_id, part_id, pin_id)como clave principal.
ypercubeᵀᴹ
Dos cosas: (a) ¿1OUT, 1IN-, 1IN +, GND, 2IN +, 2IN-, 2OUT y VCC producen instancias de 11 pines? (b) No entiendo tu esquema inicial. ¿No se puede usar un pin en más de una parte? Necesita una relación NN entre el pin y la parte, no un 1-N.
Marcus Junius Brutus
@ user34332: (a) Los números son parte de los nombres. Por ejemplo, "2OUT" es un solo pin. Aquí hay un dibujo esquemático del chip del que estoy hablando en la pregunta. (b) No estoy de acuerdo. Ciertamente, dos partes pueden tener un pin VCC (voltaje de suministro positivo, "voltaje [en] colector común"), pero son pines lógicamente diferentes. Por ejemplo, un pin VCC normalmente puede extraer 500 µA y otro diferente 250 µA.
Snowball
@Snowball Ayudaría a otros a comprender su esquema si agregara un SQL-Fiddle con datos de muestra.
ypercubeᵀᴹ

Respuestas:

13

Solución mínima

Una solución radical podría ser eliminar por pin_instcompleto:

  part ←────────── pin
                   
part_inst ←───── pin_inst

No hay nada en su pregunta que sugiera que realmente necesita la tabla redundante. Para los pins asociados a a part_inst, mire los pins de los asociados part.

Eso simplificaría el código para:

create table part (    -- using singular terms for table names
    part_id bigserial primary key
);
create table pin (
    pin_id bigserial primary key,
    part_id bigint not null references part
);
create table part_inst (
    part_inst_id bigserial primary key,
    part_id bigint not null references part
);

Pero su comentario dejó en claro que no nos saldremos con la suya ...

Alternativa si pin_instes necesaria

Incluir part_idcomo lo hizo es la solución más simple con restricciones de clave externa. No puede hacer referencia a una tabla "a dos tablas de distancia" con restricciones de clave externa .

Pero al menos puede hacerlo sin "contaminar" las claves primarias. Agregar UNIQUErestricciones .

create table part (
    part_id bigserial primary key
);
create table pin (
    pin_id bigserial primary key,
    part_id bigint not null references part,
    unique(part_id, pin_id)         -- note sequence of columns
);
create table part_inst (
    part_inst_id bigserial primary key,
    part_id bigint not null references part,
    unique(part_id, part_inst_id)
);
create table pin_inst (
    pin_inst_id bigserial primary key,
    part_inst_id bigint not null,
    pin_id bigint not null,
    part_id bigint not,
    foreign key (part_id, pin_id) references pin,
    foreign key (part_id, part_inst_id) references part_inst
);

Puse part_idprimero en las restricciones únicas. Eso es irrelevante para la integridad referencial, pero es importante para el desempeño. Las claves principales ya implementan índices para las columnas pk. Es mejor tener la otra columna primero en los índices de varias columnas que implementan las restricciones únicas. Detalles bajo estas preguntas relacionadas:

Preguntas relacionadas sobre SO:

Alternativa con desencadenantes

Podría recurrir a funciones de activación, que son más flexibles, pero un poco más complicadas y propensas a errores y un poco menos estrictas. El beneficio: podría prescindir part_inst.part_idy pin.part_id...

Erwin Brandstetter
fuente
Hay algunas columnas adicionales pin_insts, pero las omití en interés de la legibilidad ("Ignorando los campos de datos, [...]"). Por ejemplo, a pin_instpuede marcarse como entrada o salida.
Snowball
@Snowball: Hubiera sido fácil ser verdad. Expandí un poco tu solución.
Erwin Brandstetter
2
Su segunda sugerencia funciona bien para mi situación. No sabía que una clave externa podría hacer referencia a algo distinto de la clave primaria.
Bola de nieve