15

I ran into some trouble modeling an electrical schematic in SQL. The structure I'd like to capture is

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

where "inst" is short for "instance".

For example, I might have as a part an LM358 op-amp with pins 1OUT, 1IN-, 1IN+, GND, 2IN+, 2IN-, 2OUT, and VCC. I might then place this part on a schematic, creating a part_inst and 8 pin_insts.

Ignoring data fields, my initial attempt at a schema was

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

The main problem with this schema is that a pin_inst might be tied to a part_inst with part_id=1 but its pin has part_id=2.

I'd like to avoid this problem on the database level rather than the application level. So, I modified my primary keys to enforce that. I marked the changed lines with --.

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

My gripe with this method is that it pollutes the primary keys: Everywhere I refer to a part_inst, I need to keep track of both the part_inst_id and the part_id. Is there another way I can go about enforcing the constraint pin_inst.part_inst.part_id = pin_inst.pin.part_id without being overly verbose?

3
  • Two things: (a) doesn't 1OUT, 1IN-, 1IN+, GND, 2IN+, 2IN-, 2OUT, and VCC yield 11 pin instances? (b) I don't get your initial schema. Can't a pin be used in more than one part? You need a N-N relationship between pin and part not a 1-N. Feb 15, 2014 at 11:53
  • @user34332: (a) The numbers are part of the names. For example, "2OUT" is a single pin. Here's a schematic drawing of the chip I'm talking about in the question. (b) I disagree. Certainly two parts might have a VCC (positive supply voltage, "voltage [at] common collector") pin, but they're logically different pins. For example, one VCC pin might typically draw 500 µA and a different one 250 µA.
    – Snowball
    Feb 15, 2014 at 12:08
  • 1
    Closely related question. Feb 16, 2014 at 2:01

1 Answer 1

15

Minimal solution

A radical solution would be to remove pin_inst completely:

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

There is nothing in your question to suggest you actually need the redundant table. For pins associated to a part_inst, look at the pins of the associated part.

That would simplify the code to:

CREATE TABLE part (   -- with singular terms as 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
);

But your comment made clear that we won't get away with that ...

Alternative if pin_inst is needed

You cannot reference a table “two tables away” with foreign key constraints. Including part_id like you did is the simplest solution with foreign key constraints.

But you can at least make do without "polluting" the primary keys. Add UNIQUE constraints.

(Updated with modern syntax for Postgres 14.)

CREATE TABLE part (
  part_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
);

CREATE TABLE pin (
  pin_id       bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, part_id      bigint NOT NULL
, CONSTRAINT pin_part_id_fkey FOREIGN KEY (part_id) REFERENCES part
, CONSTRAINT pin_fk_uni UNIQUE (part_id, pin_id)  -- only for FK; note leading part_id
);

CREATE TABLE part_inst (
  part_inst_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, part_id      bigint NOT NULL
, CONSTRAINT part_inst_part_id_fkey FOREIGN KEY (part_id) REFERENCES part
, CONSTRAINT part_inst_fk_uni UNIQUE (part_id, part_inst_id)  -- only for FK
);

CREATE TABLE pin_inst (
  pin_inst_id  bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, part_inst_id bigint NOT NULL
, pin_id       bigint NOT NULL
, part_id      bigint NOT NULL
, CONSTRAINT pin_fkey       FOREIGN KEY (part_id, pin_id)       REFERENCES pin (part_id, pin_id)
, CONSTRAINT part_inst_fkey FOREIGN KEY (part_id, part_inst_id) REFERENCES part_inst (part_id, part_inst_id)
);

db<>fiddle here

The bold UNIQUE constraints are logically redundant, but required as target for the bold FOREIGN KEY constraints.

I put part_id first in the unique constraints (and consequently in the FK constraints, too). That is irrelevant for referential integrity, but it matters for performance. The primary keys already implement indexes for the PK columns. It's better to have the other column first in the multicolumn indexes implementing the UNIQUE constraints. See:

Related questions on SO:

Alternative with triggers

You could resort to triggers functions, which are more flexible, but a bit more complicated and error prone and a bit less strict. The benefit: you could do without part_inst.part_id and pin.part_id ...

2
  • Do we even need unique(part_id, pin_id)? Seeing as pin_id is a primary key, aren't we guaranteed that it will only be associated with a single part_id?
    – Gili
    Jun 22, 2022 at 23:43
  • 1
    @Gili: Those UNIQUE constraints are logically redundant, but required. I clarified the explanation above. While being at it, I also revised the main solution and brought it up to date. Jun 23, 2022 at 2:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.