Faire respecter les contraintes «à deux tables»

10

J'ai rencontré des problèmes pour modéliser un schéma électrique en SQL. La structure que j'aimerais capturer est

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

où "inst" est l'abréviation de "instance".

Par exemple, je pourrais avoir un partampli-op LM358 avec pins 1OUT, 1IN-, 1IN +, GND, 2IN +, 2IN-, 2OUT et V CC . Je pourrais alors placer cette partie sur un schéma, créant un part_instet 8 pin_insts.

Ignorant les champs de données, ma tentative initiale de schéma était

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

Le principal problème avec ce schéma est qu'un pin_instpeut être lié à un part_instavec part_id=1mais qu'il pina part_id=2.

Je voudrais éviter ce problème au niveau de la base de données plutôt qu'au niveau de l'application. J'ai donc modifié mes clés primaires pour appliquer cela. J'ai marqué les lignes modifiées avec --.

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

Mon reproche avec cette méthode est qu'elle pollue les clés primaires: partout où je me réfère à un part_inst, je dois garder une trace à la fois du part_inst_idet du part_id. Existe-t-il un autre moyen de faire respecter la contrainte pin_inst.part_inst.part_id = pin_inst.pin.part_idsans être trop verbeux?

Boule de neige
la source
Vous pouvez également supprimer ce pin_inst_idqui est une redondance. Vous pouvez utiliser la (part_inst_id, part_id, pin_id)clé primaire.
ypercubeᵀᴹ
Deux choses: (a) 1OUT, 1IN-, 1IN +, GND, 2IN +, 2IN-, 2OUT et VCC ne produisent-ils pas des instances de 11 broches? (b) Je ne reçois pas votre schéma initial. Une broche ne peut-elle pas être utilisée dans plusieurs parties? Vous avez besoin d'une relation NN entre la broche et la pièce, pas un 1-N.
Marcus Junius Brutus
@ user34332: (a) Les nombres font partie des noms. Par exemple, "2OUT" est une broche unique. Voici un schéma de la puce dont je parle dans la question. (b) Je ne suis pas d'accord. Certes, deux parties peuvent avoir une broche VCC (tension d'alimentation positive, "tension [au] collecteur commun"), mais ce sont logiquement des broches différentes. Par exemple, une broche VCC peut généralement tirer 500 µA et une autre 250 µA.
Snowball
@Snowball Cela aiderait les autres à comprendre votre schéma si vous ajoutiez un SQL-Fiddle avec des exemples de données.
ypercubeᵀᴹ
1
Question étroitement liée.
Erwin Brandstetter

Réponses:

13

Solution minimale

Une solution radicale pourrait être de supprimer pin_instcomplètement:

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

Rien dans votre question ne suggère que vous avez réellement besoin de la table redondante. Pour les pins associés à a part_inst, regardez les pins des associés part.

Cela simplifierait le code pour:

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

Mais votre commentaire indiquait clairement que nous n'allons pas nous en sortir avec ça ...

Alternative si pin_instnécessaire

Inclure part_idcomme vous l'avez fait est la solution la plus simple avec des contraintes de clé étrangère. Vous ne pouvez pas référencer une table "à deux tables" avec des contraintes de clé étrangère .

Mais vous pouvez au moins vous débrouiller sans «polluer» les clés primaires. Ajoutez des UNIQUEcontraintes .

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

Je mets d' part_idabord dans les contraintes uniques. Cela n'est pas pertinent pour l'intégrité référentielle, mais c'est important pour les performances. Les clés primaires implémentent déjà des index pour les colonnes pk. Il est préférable d'avoir l' autre colonne en premier dans les index multicolonnes implémentant les contraintes uniques. Détails sous ces questions connexes:

Questions connexes sur SO:

Alternative avec déclencheurs

Vous pouvez recourir à des fonctions de déclencheurs, qui sont plus flexibles, mais un peu plus compliquées et sujettes aux erreurs et un peu moins strictes. L'avantage: vous pouvez vous en passer part_inst.part_idet pin.part_id...

Erwin Brandstetter
la source
Il y a quelques colonnes supplémentaires pin_insts, mais je les ai omises dans un souci de lisibilité ("Ignorer les champs de données, [...]"). Par exemple, a pin_instpeut être marqué comme entrée ou sortie.
Snowball
@Snowball: Cela aurait été trop facile pour être vrai. J'ai développé un peu votre solution.
Erwin Brandstetter
2
Votre deuxième suggestion fonctionne bien pour ma situation. Je ne savais pas qu'une clé étrangère pouvait référencer autre chose que la clé primaire.
Snowball