如何在 Ecto 迁移中动态更新字段值?
How to update field value dynamically in Ecto migration?
我有一个用户table喜欢:
email | username
---------------+----------
123@321.com |
123@123.com |
haha@haha.com |
我想用 email
字段更新 username
字段,只需在 @
.
之前切片 email
email | username
---------------+----------
123@321.com | 123
123@123.com | 123
haha@haha.com | haha
我尝试使用以下迁移:
defmodule MyApp.Repo.Migrations.AddDefaultUsernameForUsers do
use Ecto.Migration
import Ecto.Query
def up do
from(u in MyApp.User, update: [set: [username: String.split(u.email, "@") |> List.first ]])
|> MyApp.Repo.update_all([])
end
def down do
MyApp.Repo.update_all(MyApp.User, set: [username: nil])
end
end
但是在运行迁移时,出现以下错误:
$ mix ecto.migrate
** (Ecto.Query.CompileError) `List.first(String.split(u.email(), "@"))` is not a valid query expression
我该如何解决这个问题?
您将要进行两个单独的查询。一个查询获取数据,做任何你想做的改变,然后第二个查询更新数据。类似于
Repo.all(MyApp.User)
|> Enum.map(fn u ->
username =
u.email
|> String.split("@")
|> List.first()
Ecto.Changeset.cast(u, %{username: username})
end)
|> Repo.update_all()
关于为什么你不能做你想做的事情,有几件事正在发生。
当您想在 Ecto 查询中使用 Elixir 函数或值时,通常必须使用 pin 运算符 (^
)。所以如果你想查询一个特定的 ID,你可以使用 from(u in MyApp.User, where: u.id == ^12)
。所以你的反应可能是尝试使用 ^List.first(String.split(u.email, "@"))
。但是,这不会起作用,因为...
from(u in MyApp.User)
中的u
是数据库中的记录。您无权在您的 Elixir 代码中访问它。可以使用 fragment/1
来做你想做的事情,但是你不能用常规的 Elixir 函数来操作这个值,直到你使用像我上面的例子那样把它从数据库中拉出来。
@Justin Wood 已经解释了为什么不能在更新查询中使用 Elixir 函数,所以我不再重复。在 PostgreSQL 中,您可以使用带有正则表达式的 substring
函数提取 @
之前的文本,这将与更新查询一起使用。这比加载记录然后一条一条地更新它们要快得多,但是如果不调整 SQL 片段就不能与其他数据库引擎一起使用:
from(u in MyApp.User,
update: [set: [username: fragment("substring(? from '^(.*?)@')", u.email)]])
|> MyApp.Repo.update_all([])
postgres=# select substring('123@321.com' from '^(.*?)@');
substring
-----------
123
(1 row)
我有一个用户table喜欢:
email | username
---------------+----------
123@321.com |
123@123.com |
haha@haha.com |
我想用 email
字段更新 username
字段,只需在 @
.
email
email | username
---------------+----------
123@321.com | 123
123@123.com | 123
haha@haha.com | haha
我尝试使用以下迁移:
defmodule MyApp.Repo.Migrations.AddDefaultUsernameForUsers do
use Ecto.Migration
import Ecto.Query
def up do
from(u in MyApp.User, update: [set: [username: String.split(u.email, "@") |> List.first ]])
|> MyApp.Repo.update_all([])
end
def down do
MyApp.Repo.update_all(MyApp.User, set: [username: nil])
end
end
但是在运行迁移时,出现以下错误:
$ mix ecto.migrate
** (Ecto.Query.CompileError) `List.first(String.split(u.email(), "@"))` is not a valid query expression
我该如何解决这个问题?
您将要进行两个单独的查询。一个查询获取数据,做任何你想做的改变,然后第二个查询更新数据。类似于
Repo.all(MyApp.User)
|> Enum.map(fn u ->
username =
u.email
|> String.split("@")
|> List.first()
Ecto.Changeset.cast(u, %{username: username})
end)
|> Repo.update_all()
关于为什么你不能做你想做的事情,有几件事正在发生。
当您想在 Ecto 查询中使用 Elixir 函数或值时,通常必须使用 pin 运算符 (^
)。所以如果你想查询一个特定的 ID,你可以使用 from(u in MyApp.User, where: u.id == ^12)
。所以你的反应可能是尝试使用 ^List.first(String.split(u.email, "@"))
。但是,这不会起作用,因为...
from(u in MyApp.User)
中的u
是数据库中的记录。您无权在您的 Elixir 代码中访问它。可以使用 fragment/1
来做你想做的事情,但是你不能用常规的 Elixir 函数来操作这个值,直到你使用像我上面的例子那样把它从数据库中拉出来。
@Justin Wood 已经解释了为什么不能在更新查询中使用 Elixir 函数,所以我不再重复。在 PostgreSQL 中,您可以使用带有正则表达式的 substring
函数提取 @
之前的文本,这将与更新查询一起使用。这比加载记录然后一条一条地更新它们要快得多,但是如果不调整 SQL 片段就不能与其他数据库引擎一起使用:
from(u in MyApp.User,
update: [set: [username: fragment("substring(? from '^(.*?)@')", u.email)]])
|> MyApp.Repo.update_all([])
postgres=# select substring('123@321.com' from '^(.*?)@');
substring
-----------
123
(1 row)