英文:
PostgreSQL: cascade delete on bidirectional references only for source column
问题
我有表variable
和variable_variable
。后者有两列:
variable_id_from
variable_id_to
查看下面的架构:
如果variable_variable
中有值为{variable_id_from = 1, variable_id_to = 2}
的行,意味着id = 1
的variable
引用了id = 2
的variable
。
现在我有一个新的业务要求:
假设我们有
variable A
。如果有任何其他variables
引用了A
(即variable_variable
中有任何值为{variable_id_to = A.id}
的行),则必须禁止删除A
。
假设我们有
variable B
。如果没有引用B
的变量,但与此同时B
引用了其他变量,那么可以成功删除B
,并且必须删除所有{variable_id_from = B.id}
的引用。
我考虑创建一个简单的约束并使用级联删除。看看下面的SQL片段:
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_to_fkey
FOREIGN KEY (variable_id_to) REFERENCES variable (id);
我认为这可以满足我的需求。但奇怪的是,这些测试用例未通过:
- 创建变量
A
- 创建变量
B
- 让
A
引用B
- 尝试删除
B
- 预期结果:删除变量时出现错误。实际结果:成功删除
B
以及variable_variable
中的所有相关链接。
这很奇怪。似乎variable_variable_variable_id_from_fkey
在某种程度上被触发了。有没有解决这个问题的想法?
此外,还有一个重要情况。变量可能引用自身。因此,variable_variable
表可以包含行{variable_id_from = 1, variable_id_to = 1}
。在这种情况下,删除也应该成功,并且必须按级联方式删除所有链接。
附注:我知道可以在应用程序端执行删除,但我将其视为最后的选择。整个数据库结构比我展示的要复杂得多,级联约束对于保持代码更清晰非常有帮助。
英文:
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 other variables
that reference to A
(i.e. variable_variable
has any row with value of {variable_id_to = A.id}
), then deletion of A
must be prohibited.
> Suppose we have variable B
. If there are no variables that references B
but in the same time B
references any other variables, then deletion of B
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.
答案1
得分: 1
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
CREATE TABLE variable (id int, name varchar(10))
英文:
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
CREATE TABLE variable (id int, name varchar(10))
> status
> CREATE TABLE
>
INSERT INTO variable VALUES(1,'test'),(2,'test2')
> status
> INSERT 0 2
>
CREATE tABLe variable_variable (variable_id_from int,variable_id_to int)
> status
> CREATE TABLE
>
INSERT INTO variable_variable VALUEs (3,1)
> status
> INSERT 0 1
>
CREATE OR REPLACE FUNCTION DELETE_ONLY_()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
declare
l_count integer;
begin
select count(*)
into l_count
from variable_variable WHERE variable_id_to = OLd.id;
if l_count > 0 then
raise exception 'There are still records';
end if;
return old;
end;
$$
> status
> CREATE FUNCTION
>
CREATE TRIGGER trigger_name
BEFORE DELETE
ON variable
FOR EACH ROW
EXECUTE PROCEDURE DELETE_ONLY_()
> status
> CREATE TRIGGER
>
DELETE FROM variable WHERE id = 1
> error
> ERROR: There are still records
> CONTEXT: PL/pgSQL function delete_only_() line 10 at RAISE
>
DELETE FROM variable WHERE id = 2
> status
> DELETE 1
>
SELECT * FROM variable
id | name |
---|---|
1 | test |
> ``` status | |
> SELECT 1 | |
> ``` |
答案2
得分: 0
感谢大家提供的解决方案。我成功解决了我的问题。以下是定义的约束:
-- 如果当前变量被另一个变量引用,请限制删除
ALTER TABLE variable_variable
ADD CONSTRAINT variable_variable_variable_id_to_fkey
FOREIGN KEY (variable_id_to) REFERENCES variable (id)
ON DELETE RESTRICT;
-- 如果当前变量引用其他变量,请按级联方式删除其链接
ALTER TABLE variable_variable
ADD CONSTRAINT variable_variable_variable_id_from_fkey
FOREIGN KEY (variable_id_from) REFERENCES variable (id)
ON DELETE CASCADE;
据我了解,PostgreSQL按照它们创建的顺序检查约束。所以,以下是发生的情况:
- 如果存在像
其他变量 -> 当前变量
的引用,请限制删除。否则,进行下一步。 - 如果存在像
当前变量 -> 其他变量
的引用,请按级联方式删除当前变量
及其在variable_variable
表中的链接。
然而,我还遇到了与约束无关的问题,而是与Hibernate有关。以下是Variable
实体的定义:
@Entity
@Table(name = "variable")
@Getter
@Setter(PROTECTED)
@NoArgsConstructor(access = PROTECTED)
@DynamicUpdate
public class Variable {
@EmbeddedId
private VariableId id;
@ManyToMany(fetch = LAZY)
@JoinTable(
name = "variable_variable",
joinColumns = @JoinColumn(name = "variable_id_from"),
inverseJoinColumns = @JoinColumn(name = "variable_id_to")
)
private Set<Variable> variables = new HashSet<>();
@ManyToMany(fetch = LAZY)
@JoinTable(
name = "variable_variable",
joinColumns = @JoinColumn(name = "variable_id_to", updatable = false, insertable = false),
inverseJoinColumns = @JoinColumn(name = "variable_id_from", updatable = false, insertable = false)
)
// 这个集合是只读的,不会更新
private Set<Variable> inverseVariables = new HashSet<>();
...
}
我曾经使用简单的Spring Data JPA存储库方法 delete(Variable variable)
来删除Variable
。实际上,生成的查询如下:
delete from variable_variable where variable_id_from = ?
delete from variable_variable where variable_id_to = ?
delete from variable where id = ?
据我了解,拥有方的ManyToMany
集合始终是 orphanRemoval = true
。因此,Hibernate将始终在删除实体本身之前删除ManyToMany
链接(如果我理解错误,请纠正我)。因此,数据库约束没有意义,因为Hibernate会过早地删除所有链接。
目前,我使用了原生SQL查询,并将 delete
和 deleteById
方法标记为抛出 UnsupportedOperationException
,以防止意外调用它们。无论如何,我认为这不是一个清晰的解决方案。你有没有想法如何告诉Hibernate不要在拥有方删除ManyToMany
链接?
英文:
Thanks everybody for proposed solutions. I managed to solve my problem. Here are the defined constraints:
-- If current variable is being referenced by another variable, restrict deletion
ALTER TABLE variable_variable
ADD CONSTRAINT variable_variable_variable_id_to_fkey
FOREIGN KEY (variable_id_to) REFERENCES variable (id)
ON DELETE RESTRICT;
-- If current variable reference other variable, delete its link by cascade
ALTER TABLE variable_variable
ADD CONSTRAINT variable_variable_variable_id_from_fkey
FOREIGN KEY (variable_id_from) REFERENCES variable (id)
ON DELETE CASCADE;
As I understood, PostgreSQL checks constraints in the order they were created. So, here is what happens:
- If there are any references like
Other Variable -> Current Variable
, restrict deletion. Otherwise, go the next step. - If there are any references like
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:
@Entity
@Table(name = "variable")
@Getter
@Setter(PROTECTED)
@NoArgsConstructor(access = PROTECTED)
@DynamicUpdate
public class Variable {
@EmbeddedId
private VariableId id;
@ManyToMany(fetch = LAZY)
@JoinTable(
name = "variable_variable",
joinColumns = @JoinColumn(name = "variable_id_from"),
inverseJoinColumns = @JoinColumn(name = "variable_id_to")
)
private Set<Variable> variables = new HashSet<>();
@ManyToMany(fetch = LAZY)
@JoinTable(
name = "variable_variable",
joinColumns = @JoinColumn(name = "variable_id_to", updatable = false, insertable = false),
inverseJoinColumns = @JoinColumn(name = "variable_id_from", updatable = false, insertable = false)
)
// this collection is readonly and never updates
private Set<Variable> inverseVariables = new HashSet<>();
...
}
I used to delete Variable
with simple Spring Data JPA repository method delete(Variable variable)
. Actually, that are the queries that were generated:
delete from variable_variable where variable_id_from = ?
delete from variable_variable where variable_id_to = ?
delete from variable where id = ?
As far as I understood, the owning-side ManyToMany
collections are always orphanRemoval = true
. So, Hibernate will always delete ManyToMany
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
and deleteById
methods to throw UnsupportedOperationException
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 delete ManyToMany
links on the owner side?
答案3
得分: -1
您遇到的问题是,当您删除变量B时,variable_variable_variable_id_from_fkey上的外键约束不会触发,因为它没有定义为ON DELETE CASCADE。这意味着引用B的variable_variable中的行不会被删除,因此B的删除将成功进行。
为了解决这个问题,您可以修改variable_id_to上的外键约束,使其也具有ON DELETE CASCADE:
ALTER TABLE variable_variable
ADD CONSTRAINT variable_variable_variable_id_to_fkey
FOREIGN KEY (variable_id_to) REFERENCES variable (id)
ON DELETE CASCADE;
通过这种修改,当您删除变量B时,还将删除引用B的variable_variable中的所有行(即variable_id_to等于B的id的行)。这将防止删除B,如果其他变量引用它。
为了处理变量引用自身的情况,您可以在variable表中添加一个触发器,检查变量是否引用自身,并删除variable_variable中的相应行:
CREATE TRIGGER delete_self_reference
BEFORE DELETE ON variable
FOR EACH ROW
BEGIN
DELETE FROM variable_variable WHERE variable_id_from = OLD.id AND variable_id_to = OLD.id;
END;
这个触发器将在从variable表中删除一行之前触发。如果要删除的行的id同时出现在variable_variable的一行的两列中,那么variable_variable中的相应行将被删除。这将允许删除变量,即使它引用自身。
通过这些修改,外键约束和触发器,您应该能够满足业务要求并保持数据库结构的清洁。
英文:
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:
ALTER TABLE variable_variable
ADD CONSTRAINT variable_variable_variable_id_to_fkey
FOREIGN KEY (variable_id_to) REFERENCES variable (id)
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:
CREATE TRIGGER delete_self_reference
BEFORE DELETE ON variable
FOR EACH ROW
BEGIN
DELETE FROM variable_variable WHERE variable_id_from = OLD.id AND variable_id_to = OLD.id;
END;
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论