I have two tables, SystemVariables and VariableOptions. SystemVariables should be self-explanatory, and VariableOptions contains all of the possible choices for all of the variables.
VariableOptions has a foreign key, variable_id, which states which variable it is an option for. SystemVariables has a foreign key, choice_id, which states which option is the currently selected one.
I’ve gotten around the circular relationship using use_alter on choice_id, and post_update on SystemVariables‘ choice relationship. However, I would like to add an extra database constraint that will ensure that choice_id is valid (i.e. it’s referring to an option that is referring back to it).
The logic I need, assuming that sysVar represents a row in the SystemVariables table, is basically:
VariableOptions[sysVar.choice_id].variable_id == sysVar.id
But I don’t know how to construct this kind of constraint using SQL, declarative, or any other method. If necessary I could just validate this at the application level, but I’d like to have it at the database level if possible. I’m using Postgres 9.1.
Is this possible?
You can implement that without dirty tricks. Just extend the foreign key referencing the chosen option to include
variable_idin addition tochoice_id.Here is a working demo. Temporary tables, so you can easily play with it:
Choosing an associated option is allowed:
But there is no getting out of line:
Exactly what you wanted.
All key columns
NOT NULLI think I found a better solution in this later answer:
Addressing the @ypercube’s question in the comments, to avoid entries with unknown association make all key columns
NOT NULL, including foreign keys.The circular dependency would normally make that impossible. It’s the classical chicken-egg problem: one of both has to be there first to spawn the other. But nature found a way around it, and so did Postgres: deferrable foreign key constraints.
New variables and associated options have to be inserted in the same transaction:
The
NOT NULLconstraint cannot be deferred, it is enforced immediately. But the foreign key constraint can, because we defined it that way. It is checked at the end of the transaction, which avoids the chicken-egg problem.In this edited scenario, both foreign keys are deferred. You can enter variables and options in arbitrary sequence.
You can even make it work with plain non-deferrable FK constraint if you enter related entries in both table in one statement using CTEs as detailed in the linked answer.
You may have noticed that the first foreign key constraint has no
CASCADEmodifier. (It wouldn’t make sense to allow changes tovariableoptions.variable_idto cascade back.On the other hand, the second foreign key has a
CASCADEmodifier and is definedDEFERRABLEnonetheless. This carries some limitations. The manual:NO ACTIONis the default.So, referential integrity checks on
INSERTare deferred, but the declared cascading actions onDELETEandUPDATEare not. The following is not permitted in PostgreSQL 9.0 or later because constraints are enforced after each statement:Details: