从 SQL 查询中填充虚拟字段
Fill virtual fields from a SQL query
我必须处理无法更改的数据库设置,而且我必须使用特定的 SQL 查询来计算 table 中不属于字段的值。我怎样才能在 Ecto 中完成这项工作?这是我的方法和我 运行 遇到的问题:
设置
$ mix phx.new testapp
$ cd testapp
$ mix ecto.create
$ mix phx.gen.html Shops Product products name price:float
$ mix ecto.migrate
之后我创建了几个产品。
x
我向 product
添加了一个虚拟 x
字段:
lib/testapp/shops/product.ex
defmodule Testapp.Shops.Product do
use Ecto.Schema
import Ecto.Changeset
schema "products" do
field :name, :string
field :price, :float
field :x, :integer, virtual: true # <-----
timestamps()
end
@doc false
def changeset(product, attrs) do
product
|> cast(attrs, [:name, :price])
|> validate_required([:name, :price])
end
end
然后我将以下功能添加到 Testapp.Shops
:
def execute_and_load(sql, params, model) do
result = Ecto.Adapters.SQL.query!(Repo, sql, params)
Enum.map(result.rows, &Repo.load(model, {result.columns, &1}))
end
def list_products_with_x do
sql = "SELECT *, 1 AS x FROM products;" # <- simplified
execute_and_load(sql, [], Testapp.Shops.Product)
end
1 AS x
和整个 SQL 查询只是一个简化的示例!在实际应用程序中,我必须使用 SQL 查询调用存储过程进行计算,将值存储在 x
中。所以会有某种 SQL 我无法用 Ecto 本身创建的东西。如果您对 SQL 感兴趣:
问题
SQL 查询为每个条目提供 x
的值,但 product
将 x
列为 nil
。我怎么解决这个问题?如何在 execute_and_load/3
中填写 virtual
字段?
iex(1)> Testapp.Shops.list_products_with_x
[debug] QUERY OK db=1.3ms queue=2.2ms idle=8177.7ms
SELECT *, 1 AS x FROM products; []
[
%Testapp.Shops.Product{
__meta__: #Ecto.Schema.Metadata<:loaded, "products">,
id: 1,
inserted_at: ~N[2020-02-12 07:29:36],
name: "Apple",
price: 0.5,
updated_at: ~N[2020-02-12 07:29:36],
x: nil
},
%Testapp.Shops.Product{
__meta__: #Ecto.Schema.Metadata<:loaded, "products">,
id: 2,
inserted_at: ~N[2020-02-12 07:29:47],
name: "Orange",
price: 0.75,
updated_at: ~N[2020-02-12 07:29:47],
x: nil
}
]
我愿意为给定的问题寻找替代解决方案。我无法在我的 Elixir 程序中计算 x
的值。我必须使用 SQL 来计算它,我想使用 Ecto.
一个更好的方法是 select 将您需要的所有内容都添加到一个结构中,然后将其移动到 Ecto.Struct
。我的方法如下:
def get_products() do
query = from p in Products,
select: %{name: p.name, price: p.price, x: fragment("1")}
query
|> Repo.all()
|> Enum.map(fn el -> struct(Products, el) end)
end
这种方法的优点是我不使用原始字符串查询。您的计算应该进入片段部分。
在我看来,你最好让 SQL 与片段一起工作。
Repo.all from p in Product, select: %{p | x: 1}
如果你不能让它工作,Repo.load/2
可以使用地图而不是模式。
data =
:load
|> Product.__schema__()
|> Enum.into(%{x: :integer})
|> Repo.load({columns, row})
struct(Product, data)
如果您想简化它,您可以覆盖 Product.__schema__(:load)
并使用现有的 &Repo.load(model, {result.columns, &1})
:
schema "products" do
...
end
# WARNING: This could have unintended effects
# You're probably better off not poking around in Ecto internals
defoverridable __schema__: 1
def __schema__(:load), do: [{:x, :integer} | super(:load)]
def __schema__(arg), do: super(arg)
我必须处理无法更改的数据库设置,而且我必须使用特定的 SQL 查询来计算 table 中不属于字段的值。我怎样才能在 Ecto 中完成这项工作?这是我的方法和我 运行 遇到的问题:
设置
$ mix phx.new testapp
$ cd testapp
$ mix ecto.create
$ mix phx.gen.html Shops Product products name price:float
$ mix ecto.migrate
之后我创建了几个产品。
x
我向 product
添加了一个虚拟 x
字段:
lib/testapp/shops/product.ex
defmodule Testapp.Shops.Product do
use Ecto.Schema
import Ecto.Changeset
schema "products" do
field :name, :string
field :price, :float
field :x, :integer, virtual: true # <-----
timestamps()
end
@doc false
def changeset(product, attrs) do
product
|> cast(attrs, [:name, :price])
|> validate_required([:name, :price])
end
end
然后我将以下功能添加到 Testapp.Shops
:
def execute_and_load(sql, params, model) do
result = Ecto.Adapters.SQL.query!(Repo, sql, params)
Enum.map(result.rows, &Repo.load(model, {result.columns, &1}))
end
def list_products_with_x do
sql = "SELECT *, 1 AS x FROM products;" # <- simplified
execute_and_load(sql, [], Testapp.Shops.Product)
end
1 AS x
和整个 SQL 查询只是一个简化的示例!在实际应用程序中,我必须使用 SQL 查询调用存储过程进行计算,将值存储在 x
中。所以会有某种 SQL 我无法用 Ecto 本身创建的东西。如果您对 SQL 感兴趣:
问题
SQL 查询为每个条目提供 x
的值,但 product
将 x
列为 nil
。我怎么解决这个问题?如何在 execute_and_load/3
中填写 virtual
字段?
iex(1)> Testapp.Shops.list_products_with_x
[debug] QUERY OK db=1.3ms queue=2.2ms idle=8177.7ms
SELECT *, 1 AS x FROM products; []
[
%Testapp.Shops.Product{
__meta__: #Ecto.Schema.Metadata<:loaded, "products">,
id: 1,
inserted_at: ~N[2020-02-12 07:29:36],
name: "Apple",
price: 0.5,
updated_at: ~N[2020-02-12 07:29:36],
x: nil
},
%Testapp.Shops.Product{
__meta__: #Ecto.Schema.Metadata<:loaded, "products">,
id: 2,
inserted_at: ~N[2020-02-12 07:29:47],
name: "Orange",
price: 0.75,
updated_at: ~N[2020-02-12 07:29:47],
x: nil
}
]
我愿意为给定的问题寻找替代解决方案。我无法在我的 Elixir 程序中计算 x
的值。我必须使用 SQL 来计算它,我想使用 Ecto.
一个更好的方法是 select 将您需要的所有内容都添加到一个结构中,然后将其移动到 Ecto.Struct
。我的方法如下:
def get_products() do
query = from p in Products,
select: %{name: p.name, price: p.price, x: fragment("1")}
query
|> Repo.all()
|> Enum.map(fn el -> struct(Products, el) end)
end
这种方法的优点是我不使用原始字符串查询。您的计算应该进入片段部分。
在我看来,你最好让 SQL 与片段一起工作。
Repo.all from p in Product, select: %{p | x: 1}
如果你不能让它工作,Repo.load/2
可以使用地图而不是模式。
data =
:load
|> Product.__schema__()
|> Enum.into(%{x: :integer})
|> Repo.load({columns, row})
struct(Product, data)
如果您想简化它,您可以覆盖 Product.__schema__(:load)
并使用现有的 &Repo.load(model, {result.columns, &1})
:
schema "products" do
...
end
# WARNING: This could have unintended effects
# You're probably better off not poking around in Ecto internals
defoverridable __schema__: 1
def __schema__(:load), do: [{:x, :integer} | super(:load)]
def __schema__(arg), do: super(arg)