postgresql PG::外键冲突:错误:更新或删除表“xxx”违反了外键约束

wwodge7n  于 2022-12-18  发布在  PostgreSQL
关注(0)|答案(3)|浏览(270)

我有几个表,它们都有关联的外键约束,每个表都以分层方式引用另一个表,如下所述。
当我试图摧毁一个公司,至少有1个项目,至少有1个任务,至少有1个任务时间像这样...

irb(main):014:0> Company.first.destroy

我得到了下面的输出和错误。我现在的印象是,仅仅使用dependent: :delete_all并不能处理外键约束,这是真的吗?如果是这样,我该如何处理这种情况?我知道before_destroy回调,在这种情况下我必须使用它吗?如果是这样,如何临时禁用外键约束以销毁该行中所有关联的行?什么?更让人困惑的是,我有一个旧的Rails项目,它有相同的表/模型设置,只是它是一个带有外键约束的单一model_a has_many model_bs, dependent: delete_all关系,我可以ModelB.destroy_all,它工作正常。所以我不明白,我也读过一些帖子,在表上设置级联删除是有效的,还有一些帖子说如果你自己用代码处理它,就不需要这样做;如果解决方案不太复杂的话,我想在我的代码中处理这个问题。

Company Load (0.4ms)  SELECT  "companies".* FROM "companies" ORDER BY 
                              "companies"."id" ASC LIMIT $1 [["LIMIT", 1]]
   (0.2ms)  BEGIN
             SQL (0.9ms)  DELETE FROM "projects" 
                          WHERE "projects"."company_id" = $1 [["company_id", 3]]
   (0.1ms)  ROLLBACK
             Traceback (most recent call last):
   1: from (irb):13
             ActiveRecord::InvalidForeignKey (PG::ForeignKeyViolation: ERROR:  update or delete on table "projects" violates foreign key constraint "fk_rails_02e851e3b7" on table "tasks"
                          DETAIL:  Key (id)=(4) is still referenced from table "tasks".
                        : DELETE FROM "projects" WHERE "projects"."company_id" = $1)

架构

# /db/schema.rb

create_table "companies", force: :cascade do |t|
...
end

create_table "projects", force: :cascade do |t|
...
end

create_table "tasks", force: :cascade do |t|
...
end

create_table "task_times", force: :cascade do |t|
...
end
...

add_foreign_key "projects", "companies"
add_foreign_key "tasks", "projects"
add_foreign_key "task_times", "tasks"

型号

# /models/company.rb

class Company < ApplicationRecord
  has_many :projects, dependent: :delete_all
...
end

# /models/project.rb

class Project < ApplicationRecord
  has_many :tasks, dependent: :delete_all
...
end

# /models/task.rb

class Task < ApplicationRecord
  has_many :task_times, dependent: :delete_all
...
end

# /models/task_time.rb

class TaskTime < ApplicationRecord
...
end
y0u0uwnf

y0u0uwnf1#

从精细手册:

has_many(名称,作用域= nil,选项= {},扩展名&)

[...]

  • :dependent

控制当相关对象的所有者被销毁时,它们会发生什么。注意,这些都是作为回调实现的,并且Rails会按顺序执行回调。因此,其他类似的回调可能会影响:dependent行为,而:dependent行为可能会影响其他回调。

  • :destroy导致所有关联的对象也被销毁。
  • :delete_all导致所有关联的对象直接从数据库中删除(因此将不执行回调)。
  • [...]

所以:delete_all确实处理了外键,但是由于没有调用回调函数,所以它只处理了一个层次,在Company中是这样的:

has_many :projects, dependent: :delete_all

意味着调用某个公司的#destroy将直接从数据库中删除关联的projects,但不会看到以下内容:

has_many :tasks, dependent: :delete_all

您在Project中拥有的项目,并且您最终尝试删除仍在tasks中引用的项目,正如错误消息所指示的那样。
您可以将所有关联切换到dependent: :destroy,这将在销毁它们之前从数据库中取出所有内容,并调用回调(这将从数据库中加载更多内容,但销毁它们将从数据库中加载更多内容...)。最终结果将是大量数据库活动,但所有外键都将得到正确遵循。
或者,您可以通过在外键约束上指定on delete cascade,将逻辑放入数据库中它通常所属的位置:
CASCADE指定删除引用行时,引用该行的行也应自动删除
您的add_foreign_key调用如下所示:

add_foreign_key "projects", "companies", on_delete: :cascade
add_foreign_key "tasks", "projects", on_delete: :cascade
add_foreign_key "task_times", "tasks", on_delete: :cascade


在这种情况下,您可能希望在您的模型中留下dependent: :delete_all作为正在发生的事情的提醒,或者您可以给自己留下评论。

6pp0gazn

6pp0gazn2#

我也遇到了同样的问题,我只需要先销毁关联表中的记录,然后再销毁主表,就完成了我所需要的工作,这样就不会违反引用完整性,因此也就不会出现错误。
我从this question得到了这个答案

hgb9j2n6

hgb9j2n63#

我也有同样的问题,我用

rake db:reset

相关问题