Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/elixir/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Postgresql 使用自定义逻辑将字段迁移到不同类型的正确方法?_Postgresql_Elixir_Phoenix Framework_Database Migration_Ecto - Fatal编程技术网

Postgresql 使用自定义逻辑将字段迁移到不同类型的正确方法?

Postgresql 使用自定义逻辑将字段迁移到不同类型的正确方法?,postgresql,elixir,phoenix-framework,database-migration,ecto,Postgresql,Elixir,Phoenix Framework,Database Migration,Ecto,我有一个列:from,它最初的类型是{:array,:string}。现在,我想将此列迁移为:string类型,将数组的第一个条目作为新值 在Rails中,您可以在迁移过程中使用一些自定义逻辑来实现这一点。我正试图用EXTO做同样的事情,但由于模式验证和变更集错误,我遇到了问题 defmodule Assistant.Repo.Migrations.ChangeFromFieldOnMails do use Ecto.Migration def up do dict_of_fr

我有一个列
:from
,它最初的类型是
{:array,:string}
。现在,我想将此列迁移为
:string类型,将数组的第一个条目作为新值

在Rails中,您可以在迁移过程中使用一些自定义逻辑来实现这一点。我正试图用EXTO做同样的事情,但由于模式验证和变更集错误,我遇到了问题

defmodule Assistant.Repo.Migrations.ChangeFromFieldOnMails do
  use Ecto.Migration

  def up do
    dict_of_froms =
      Assistant.Mail
      |> Assistant.Repo.all()
      |> Enum.reduce(%{}, fn mail, acc ->
        Map.put(acc, mail.id, List.first(mail.from))
      end)

    alter table(:mails) do
      remove :from
      add :from, :string
    end

    Assistant.Mail
    |> Assistant.Repo.all()
    |> Enum.each(fn mail ->
      changeset = Ecto.Changeset.change(mail, from: Map.get(dict_of_froms, mail.id))
      Assistant.Repo.update!(changeset)
    end)
  end

  def down do
    dict_of_froms =
      Assistant.Mail
      |> Assistant.Repo.all()
      |> Enum.reduce(%{}, fn mail, acc ->
        Map.put(acc, mail.id, [mail.from])
      end)

    alter table(:mails) do
      remove :from
      add :from, {:array, :string}
    end

    Assistant.Mail
    |> Assistant.Repo.all()
    |> Enum.each(fn mail ->
      changeset = Ecto.Changeset.change(mail, from: Map.get(dict_of_froms, mail.id))
      Assistant.Repo.update!(changeset)
    end)
  end
end


问题是,在我的
Mail
模式中,我还必须将
field:from,{:array,:string}
更改为
field:from,:string
,这会导致验证问题

up
步骤中,
Assistant.Repo.all()
将失败,因为由于类型不匹配,Ecto无法从旧数据库加载
from
字段

down
步骤中,
Assistant.Repo.update!(变更集)
将失败,因为
exto.changeset
报告了
:from
上的类型不匹配错误

在Rails中,没有对模式进行严格的检查,因此您可以不受代码的影响


使用EXTO执行此类迁移的正确方法是什么?除了编写自定义SQL之外,没有其他方法了吗?

您需要避免在迁移中使用结构和变更集。使用
Repo.insert_all
Repo.update_all
和模式命名

defmodule Assistant.Repo.Migrations.ChangeFromFieldOnMails do
  use Ecto.Migration
  import Ecto.Query

  def up do
    dict_of_froms =
      "mails"                # table name as string
      |> Assistant.Repo.all()
      |> Enum.reduce(%{}, fn mail, acc ->
        Map.put(acc, mail.id, List.first(mail.from))
      end)

    alter table(:mails) do
      remove :from
      add :from, :string
    end

    dict_of_froms
    |> Enum.each(fn {id, from} ->   # changed this cycle little bit, so it would
         "mails"                    # update record only if we have `from` for it
         |> where(id: ^id)
         |> update(set: [from: ^from])
         |> Repo.update_all()
    end) 
  end

  def down do
    dict_of_froms =
      "mails"
      |> Assistant.Repo.all()
      |> Enum.reduce(%{}, fn mail, acc ->
        Map.put(acc, mail.id, [mail.from])
      end)

    alter table(:mails) do
      remove :from
      add :from, {:array, :string}
    end

    dict_of_froms
    |> Enum.each(fn {id, from} ->   # changed this cycle little bit, so it would
         "mails"                    # update record only if we have `from` for it
         |> where(id: ^id)
         |> update(set: [from: ^from])
         |> Repo.update_all()
    end) 
  end
end


我不确定所有的代码都是干净的和可编译的,但希望我的想法是清楚的

基于apelsinka223的解决方案,我能够使它编译并工作

有几点值得注意:

  • 我不得不中途调用
    up
    down
    函数,否则无法及时删除和添加列

  • 如果查询不是基于模式的,则需要在查询中显式地使用
    select
    语句,以便Ecto运行它

  • update\u all()
    至少需要两个参数。可以将
    []
    作为第二个参数传入


谢谢我认为我需要的正是一种绕过结构和变更集验证的方法。明天我将修改并测试解决方案。哪里定义了
Where()
update()
?您有一个
useConto.Migration
语句,但我看了一下。娜达。好的,我在
exto.Query
@7stud中找到了它们。你是对的,我在示例中添加了
import-exto.Query
,我能够使代码编译并在一些修改后工作。我把最后的代码贴在下面。
defmodule Assistant.Repo.Migrations.ChangeFromFieldOnMails do
  use Ecto.Migration
  import Ecto.Query, only: [from: 2]
  alias Assistant.Repo

  def up do
    query = from(m in "mails", select: {m.id, m.from})

    dict_of_froms =
      query
      |> Repo.all()
      |> Enum.reduce(%{}, fn {id, from}, acc ->
        Map.put(acc, id, List.first(from))
      end)

    alter table(:mails) do
      remove :from
      add :from, :string
    end

    flush()

    dict_of_froms
    |> Enum.each(fn {id, fr} ->
      query =
        from(m in "mails",
          where: m.id == ^id,
          update: [set: [from: ^fr]]
        )

      Repo.update_all(query, [])
    end)
  end

  def down do
    query = from(m in "mails", select: {m.id, m.from})

    dict_of_froms =
      query
      |> Repo.all()
      |> Enum.reduce(%{}, fn {id, from}, acc ->
        Map.put(acc, id, [from])
      end)

    alter table(:mails) do
      remove :from
      add :from, {:array, :string}
    end

    flush()

    dict_of_froms
    |> Enum.each(fn {id, fr} ->
      query =
        from(m in "mails",
          where: m.id == ^id,
          update: [set: [from: ^fr]]
        )

      Repo.update_all(query, [])
    end)
  end
end