我有一个用户表,如:
email | username
---------------+----------
123@321.com |
123@123.com |
haha@haha.com |
我希望按username
字段更新email
字段,只需在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
我该如何解决这个问题?
答案 0 :(得分:2)
您将要进行两次单独的查询。一个查询来获取数据,执行您想要的任何更改,然后是第二个查询来更新该数据。
的内容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, "@"))
。但是,这不起作用,因为......
u
中的from(u in MyApp.User)
是数据库中的记录。您无法在Elixir代码中访问它。可能会使用fragment/1
来执行您要执行的操作,但是您不能使用常规Elixir函数操作该值,直到您使用上面的示例实际将其从数据库中拉出来。
答案 1 :(得分:2)
@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)