I have tables variable
and variable_variable
. The last one holds 2 columns:
variable_id_from
variable_id_from
Look at the schema below:
If variable_variable
has row with values {variable_id_from = 1, variable_id_to = 2}
, it means that variable
with id = 1
references to variable
with id = 2
.
Now I have a new business requirement:
Suppose we have
variable A
. If there any othervariables
that reference toA
(i.e.variable_variable
has any row with value of{variable_id_to = A.id}
), then deletion ofA
must be prohibited.
Suppose we have
variable B
. If there are no variables that referencesB
but in the same timeB
references any other variables, then deletion ofB
should proceed successfully and all references where{variable_id_from = B.id}
has to be deleted as well.
I was thinking to create one simple constraint and with the cascade delete. Look at the SQL snippet below:
ALTER TABLE variable_variable
ADD CONSTRAINT variable_variable_variable_id_from_fkey
FOREIGN KEY (variable_id_from) REFERENCES variable (id)
ON DELETE CASCADE;
ALTER TABLE variable_variable
ADD CONSTRAINT variable_variable_variable_id_from_fkey
FOREIGN KEY (variable_id_to) REFERENCES variable (id);
I thought it’s to going to serve my needs exactly. But strangely, this test cases doesn’t pass:
- Create variable
A
- Create variable
B
- Make
A
referenceB
- Try to delete
B
- Expected: error during variable deletion. Actual:
B
and all corresponding links invariable_variable
are successfully deleted.
That’s strange. Seems like the variable_variable_variable_id_from_fkey
is somehow triggered. Any ideas how to solve this problem?
Besides, there is also an important case. Variable might reference itself. So, the variable_variable
table can contain row {variable_id_from = 1, variable_id_to = 1}
. In this case, the deletion should also pass successfully and all the links has to be deleted by cascade.
P.S. I know I can perform the deletion on the application side but I’m considering this decision as the last resort. The entire database structure is much more complicated than I’ve shown. And cascade constraints are really helpful to keep code cleaner.
3
Answers
Thanks everybody for proposed solutions. I managed to solve my problem. Here are the defined constraints:
As I understood, PostgreSQL checks constraints in the order they were created. So, here is what happens:
Other Variable -> Current Variable
, restrict deletion. Otherwise, go the next step.Current Variable -> Other Variable
, deleteCurrent Variable
and its links in thevariable_variable
table on cascade.However, I had another problem not with constraints but with Hibernate. Here is the
Variable
entity definition:I used to delete
Variable
with simple Spring Data JPA repository methoddelete(Variable variable)
. Actually, that are the queries that were generated:As far as I understood, the owning-side
ManyToMany
collections are alwaysorphanRemoval = true
. So, Hibernate will always deleteManyToMany
links before removing the entity itself (correct me if I'm wrong). Therefore, the DB constraint meant nothing because Hibernate deleted all links prematurely.For now I put native SQL query and marked
delete
anddeleteById
methods to throwUnsupportedOperationException
so nobody call them accidentally. Anyway, I don't think that's a clear solution. Do you have any ideas how I can tell Hibernate not to deleteManyToMany
links on the owner side?The issue you are facing is that when you delete variable B, the foreign key constraint variable_variable_variable_id_from_fkey on variable_id_to is not triggered because it is not defined with ON DELETE CASCADE. This means that the rows in variable_variable that reference B are not deleted, and therefore, the deletion of B proceeds successfully.
To solve this problem, you can modify the foreign key constraint on variable_id_to to also have ON DELETE CASCADE:
With this modification, when you delete variable B, all rows in variable_variable that reference B (i.e., where variable_id_to is equal to B’s id) will also be deleted. This will prevent the deletion of B if any other variables reference it.
To handle the case where a variable references itself, you can add a trigger to the variable table that checks if a variable is referencing itself and deletes the corresponding rows in variable_variable:
This trigger will be triggered before a row is deleted from the variable table. If the row being deleted has an id that appears in both columns of a row in variable_variable, then the corresponding row in variable_variable will be deleted. This will allow a variable to be deleted even if it references itself.
With these modifications, the foreign key constraints and the trigger, you should be able to satisfy your business requirements and keep your database structure clean.
think about it
A forign key would with the addition on deleet cascade, delete also n the bridge table all records
A BEFORE DELETE TRIGGER will do the job, as it will check every deleted row, if there are records in variable_variable
see example
fiddle