【问题标题】:Modify foreign key in Ecto在 Ecto 中修改外键
【发布时间】:2016-05-10 13:49:19
【问题描述】:

我有这个已经运行并发送到上游的原始迁移:

create table(:videos) do
  add :url, :string
  add :title, :string
  add :description, :text
  add :user_id, references(:users, on_delete: :nothing)

  timestamps
end
create index(:videos, [:user_id])

现在我希望将user_id 上的外键更改为级联删除,这样当用户被删除时,他的所有相关视频也将被删除。

我尝试了以下迁移:

alter table(:videos) do
  modify :user_id, references(:users, on_delete: :delete_all)
end

但这会引发错误:

(Postgrex.Error) ERROR (duplicate_object): constraint "videos_user_id_fkey" for relation "videos" already exists

如何制定一个迁移脚本来根据我的要求更改此外键?


更新

我最终得到了以下解决方案:

def up do
  execute "ALTER TABLE videos DROP CONSTRAINT videos_user_id_fkey"
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :delete_all)
  end
end

def down do
  execute "ALTER TABLE videos DROP CONSTRAINT videos_user_id_fkey"
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :nothing)
  end
end

这会在 ecto 尝试重新创建它之前删除约束。

【问题讨论】:

  • 您的问题有有效的答案,最好将其作为单独的答案发布,以便任何人都可以投票。

标签: elixir phoenix-framework ecto


【解决方案1】:

我不确定它何时被添加到 Ecto,但至少在 2.1.6 中不再需要原始 SQL。 drop/1 现在 supports constraints(drop_if_exists/1 没有):

def up do
  drop constraint(:videos, "videos_user_id_fkey")
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :delete_all)
  end
end

def down do
  drop constraint(:videos, "videos_user_id_fkey")
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :nothing)
  end
end

【讨论】:

  • 目前不支持 mysql - (ArgumentError) MySQL adapter does not support constraints
【解决方案2】:

您可以在调用alter之前删除索引:

drop_if_exists index(:videos, [:user_id])
alter table(:videos) do
  modify :user_id, references(:users, on_delete: :delete_all)
end

做相反的事情有点棘手:

execute "ALTER TABLE videos DROP CONSTRAINT videos_user_id_fkey"
create_if_not_exists index(:videos, [:user_id])

【讨论】:

  • 我最终做了一个变体。
  • drop_if_exists index(:videos, [:user_id]) 行没有意义,因为它删除了普通索引,而不是外键约束。 Jessie 的答案是更好的 IMO:stackoverflow.com/a/43572806/175830
  • 这似乎不再起作用了。 Max Clarke 下面的解决方案有效。
【解决方案3】:

我最终得到了以下解决方案:

def up do
  execute "ALTER TABLE videos DROP CONSTRAINT videos_user_id_fkey"
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :delete_all)
  end
end

def down do
  execute "ALTER TABLE videos DROP CONSTRAINT videos_user_id_fkey"
  alter table(:videos) do
    modify :user_id, references(:users, on_delete: :nothing)
  end
end

这会在 ecto 尝试重新创建它之前删除约束

从问题中复制。

【讨论】:

  • 这个应该更高
  • 不应该使用flush() 吗? drop constraint(: videos, "videos_user_id_fkey") 应该可以工作。
【解决方案4】:

我认为alter table 无法实现。例如根据this answer Postgres 不允许修改ALTER TABLE 语句中的约束。 MySQL also doesn't allow modifying constraints.

如果您没有任何数据,最简单的做法是删除该字段并将其添加回来。否则,您需要使用带有 execute 的原始 SQL

【讨论】:

  • 我希望有更好的方法。在这个特定的示例中,我可以删除该列并重新添加它,但如果这是一个真实世界的情况,该列可能已经包含有价值的信息,我会非常抱歉丢失 :)
【解决方案5】:

在Ecto SQL 3.4.3:

“如果:from 的值是%Reference{},适配器将在修改类型之前尝试删除相应的外键约束。”

modify :user_id, references(:users, on_delete: :delete_all), from: references(:users)

应该可以。在进行回滚时,我发现这可以清除 FK 并删除列:

remove :user_id, references(:users)

【讨论】:

    【解决方案6】:

    from 选项在Ecto 3.0 中引入,因此您可以以更优雅的方式设置迁移:

    defmodule App.Migrations.ChangeVideosUserConstraint do
      use Ecto.Migration
      def change do
        alter table(:videos) do
          modify(:user_id, references(:users, on_delete: :delete_all), from: references(:users))
        end
      end
    end
    

    【讨论】:

      猜你喜欢
      • 2016-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-13
      • 1970-01-01
      • 1970-01-01
      • 2016-12-17
      • 1970-01-01
      相关资源
      最近更新 更多