Aplicação de restrições a “duas mesas de distância”

10

Eu tive alguns problemas ao modelar um esquema elétrico no SQL. A estrutura que eu gostaria de capturar é

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

onde "inst" é a abreviação de "instance".

Por exemplo, eu posso ter como um partamplificador operacional LM358 com pins 1OUT, 1IN-, 1IN +, GND, 2IN +, 2IN-, 2OUT e V CC . Eu poderia então colocar esta parte em um esquema, criando a part_inste 8 pin_insts.

Ignorando os campos de dados, minha tentativa inicial de um esquema foi

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
);

O principal problema com esse esquema é que um pin_instpode estar vinculado a um part_instcom part_id=1mas pintem part_id=2.

Eu gostaria de evitar esse problema no nível do banco de dados e não no nível do aplicativo. Então, eu modifiquei minhas chaves primárias para impor isso. Marquei as linhas alteradas com --.

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              --
);

Minha queixa com esse método é que polui as chaves primárias: em todos os lugares em que me refiro a part_inst, preciso acompanhar tanto o part_inst_idquanto o part_id. Existe outra maneira de impor a restrição pin_inst.part_inst.part_id = pin_inst.pin.part_idsem ser excessivamente detalhado?

Bola de neve
fonte
Você também pode remover o pin_inst_idque é uma redundância. Você pode usar a (part_inst_id, part_id, pin_id)chave primária.
ypercubeᵀᴹ
Duas coisas: (a) 1OUT, 1IN-, 1IN +, GND, 2IN +, 2IN-, 2OUT e VCC produzem 11 instâncias de pinos? (b) Eu não entendo seu esquema inicial. Um alfinete não pode ser usado em mais de uma peça? Você precisa de um relacionamento NN entre o pino e a peça e não um 1-N.
Marcus Junius Brutus
@ user34332: (a) Os números fazem parte dos nomes. Por exemplo, "2OUT" é um único pino. Aqui está um desenho esquemático do chip de que estou falando na pergunta. (b) eu discordo. Certamente duas partes podem ter um pino VCC (tensão de alimentação positiva, "coletor de tensão [no] coletor comum"), mas são pinos logicamente diferentes. Por exemplo, um pino VCC pode tipicamente extrair 500 µA e outro 250 µA.
Snowball
@ Snowball Ajudaria outras pessoas a entender seu esquema se você adicionasse um SQL-Fiddle com dados de amostra.
ypercubeᵀᴹ
11
Pergunta intimamente relacionada.
Erwin Brandstetter

Respostas:

13

Solução mínima

Uma solução radical pode ser remover pin_instcompletamente:

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

Não há nada na sua pergunta que sugira que você realmente precise da tabela redundante. Para pins associado a a part_inst, observe os pins do associado part.

Isso simplificaria o 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
);

Mas seu comentário deixou claro que não vamos nos safar disso ...

Alternativa se pin_instfor necessário

Incluir part_idcomo você fez é a solução mais simples com restrições de chave estrangeira. Você não pode fazer referência a uma tabela "a duas tabelas de distância" com restrições de chave estrangeira .

Mas você pode pelo menos se contentar sem "poluir" as chaves primárias. Adicione UNIQUErestrições .

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
);

Coloquei part_idprimeiro as restrições únicas. Isso é irrelevante para a integridade referencial, mas é importante para o desempenho. As chaves primárias já implementam índices para as colunas pk. É melhor ter a outra coluna primeiro nos índices de várias colunas implementando as restrições exclusivas. Detalhes sobre estas perguntas relacionadas:

Perguntas relacionadas ao SO:

Alternativa com gatilhos

Você pode recorrer a funções de gatilhos, que são mais flexíveis, mas um pouco mais complicadas, propensas a erros e um pouco menos rigorosas. O benefício: você poderia prescindir part_inst.part_ide pin.part_id...

Erwin Brandstetter
fonte
Existem algumas colunas adicionais pin_insts, mas eu as omiti no interesse da legibilidade ("Ignorando campos de dados, [...]"). Por exemplo, a pin_instpode ser marcado como uma entrada ou saída.
Snowball
@ Snowball: Teria sido fácil demais para ser verdade. Expandi um pouco sua solução.
Erwin Brandstetter
2
Sua segunda sugestão funciona bem para a minha situação. Eu não sabia que uma chave estrangeira poderia fazer referência a outra coisa que não a chave primária.
Snowball