将多个可链接的 update_all 调用减少到单个 UPDATE 语句中
Reducing multiple chainable update_all calls into a single UPDATE statement
所以假设我有一个用户模块,它有 2 个函数可以接受变更集并向其添加更改,就像这样 ->
defmodule MyApp.User do
def confirm(changeset_or_struct) do
changeset_or_struct
|> Ecto.Changeset.change(confirmed: true, confirmed_at: Timex.now())
end
def update_session(changeset_or_struct, ip_address) do
changeset_or_struct
|> Ecto.Changeset.change(session_token: "token", ip_address: ip_address)
end
end
因此,如果我需要将这两项更改应用到特定用户并保存它们,我可以像这样轻松地链接这些函数调用
some_user
|> User.confirm()
|> User.update_session("ip_address")
|> Repo.update!()
所以一切都很好。
现在,假设出于某种原因,我需要以原子方式同时为大量用户确认和更新会话。显然获取所有这些用户,锁定他们然后在事务中一个一个地更新并不是一个很好的选择所以我们需要利用 update_all
函数只发送一个 UPDATE
语句。
所以我们需要的是这个 ->
User
|> Repo.update_all([set: confirmed: true, confirmed_at: Timex.now(), session_token: "token", ip_address: "ip_address"])
但是在 User
模块之外调用此代码似乎不是一个好主意,因为我只希望 User
模块知道如何处理确认和会话更新。所以下一个明显的决定是在 User
模块中创建一个函数并将该代码放在那里,但它的问题是它不灵活,因为这两个操作(确认和会话更新)现在耦合在一起。但是我们可以把它分成两个函数
def confirm_many(query) do
query
|> Repo.update_all([set: confirmed: true, confirmed_at: Timex.now()])
end
def update_session_many(query, ip_address) do
query
|> Repo.update_all([set: session_token: "token", ip_address: ip_address])
end
虽然现在它们没有耦合,但它们都不是可链接的,所以即使我可以单独使用这两个,很可能我最终会得到一段丑陋的代码和一堆 UPDATE
当它可以很容易地减少到只有一个时的陈述。那么现在的问题是:
我该怎么做?有没有办法链接一堆独立的更新,最终可以减少到单个 update_all
调用?
所以底线是我希望能够做这样的事情:
User
|> where([u], u.id in bunch_of_ids)
|> User.confirm_many()
|> User.update_session_many("ip_address")
|> Repo.update_all()
您可以使用 Ecto.Query.update
增量构建更新查询,最后只将其传递给 Repo.update_all
:
defmodule MyApp.User do
...
def confirm_many(query) do
query
|> update([set: [confirmed: true, confirmed_at: ^Ecto.DateTime.utc()]])
end
def update_session_many(query, ip_address) do
query
|> update([set: [session_token: "token", ip_address: ^ip_address]])
end
end
有了这个,下面的代码:
import Ecto.Query
alias MyApp.{Repo, User}
User
|> where([u], u.id in [1, 2, 3])
|> User.confirm_many()
|> User.update_session_many("ip_address")
|> Repo.update_all([])
执行查询:
UPDATE "users" AS u0
SET "confirmed" = TRUE,
"confirmed_at" = ,
"session_token" = 'token',
"ip_address" =
WHERE (u0."id" IN (1,2,3))
[{{2017, 5, 21}, {11, 13, 43, 0}}, "ip_address"]
所以假设我有一个用户模块,它有 2 个函数可以接受变更集并向其添加更改,就像这样 ->
defmodule MyApp.User do
def confirm(changeset_or_struct) do
changeset_or_struct
|> Ecto.Changeset.change(confirmed: true, confirmed_at: Timex.now())
end
def update_session(changeset_or_struct, ip_address) do
changeset_or_struct
|> Ecto.Changeset.change(session_token: "token", ip_address: ip_address)
end
end
因此,如果我需要将这两项更改应用到特定用户并保存它们,我可以像这样轻松地链接这些函数调用
some_user
|> User.confirm()
|> User.update_session("ip_address")
|> Repo.update!()
所以一切都很好。
现在,假设出于某种原因,我需要以原子方式同时为大量用户确认和更新会话。显然获取所有这些用户,锁定他们然后在事务中一个一个地更新并不是一个很好的选择所以我们需要利用 update_all
函数只发送一个 UPDATE
语句。
所以我们需要的是这个 ->
User
|> Repo.update_all([set: confirmed: true, confirmed_at: Timex.now(), session_token: "token", ip_address: "ip_address"])
但是在 User
模块之外调用此代码似乎不是一个好主意,因为我只希望 User
模块知道如何处理确认和会话更新。所以下一个明显的决定是在 User
模块中创建一个函数并将该代码放在那里,但它的问题是它不灵活,因为这两个操作(确认和会话更新)现在耦合在一起。但是我们可以把它分成两个函数
def confirm_many(query) do
query
|> Repo.update_all([set: confirmed: true, confirmed_at: Timex.now()])
end
def update_session_many(query, ip_address) do
query
|> Repo.update_all([set: session_token: "token", ip_address: ip_address])
end
虽然现在它们没有耦合,但它们都不是可链接的,所以即使我可以单独使用这两个,很可能我最终会得到一段丑陋的代码和一堆 UPDATE
当它可以很容易地减少到只有一个时的陈述。那么现在的问题是:
我该怎么做?有没有办法链接一堆独立的更新,最终可以减少到单个 update_all
调用?
所以底线是我希望能够做这样的事情:
User
|> where([u], u.id in bunch_of_ids)
|> User.confirm_many()
|> User.update_session_many("ip_address")
|> Repo.update_all()
您可以使用 Ecto.Query.update
增量构建更新查询,最后只将其传递给 Repo.update_all
:
defmodule MyApp.User do
...
def confirm_many(query) do
query
|> update([set: [confirmed: true, confirmed_at: ^Ecto.DateTime.utc()]])
end
def update_session_many(query, ip_address) do
query
|> update([set: [session_token: "token", ip_address: ^ip_address]])
end
end
有了这个,下面的代码:
import Ecto.Query
alias MyApp.{Repo, User}
User
|> where([u], u.id in [1, 2, 3])
|> User.confirm_many()
|> User.update_session_many("ip_address")
|> Repo.update_all([])
执行查询:
UPDATE "users" AS u0
SET "confirmed" = TRUE,
"confirmed_at" = ,
"session_token" = 'token',
"ip_address" =
WHERE (u0."id" IN (1,2,3))
[{{2017, 5, 21}, {11, 13, 43, 0}}, "ip_address"]