【问题标题】:Fill virtual fields from a SQL query从 SQL 查询中填充虚拟字段
【发布时间】:2020-05-27 17:19:12
【问题描述】:

我必须处理我无法更改的数据库设置,并且我必须使用特定的 SQL 查询来计算不是表中字段的值。我怎样才能在 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 中。所以会有一些我不能用 Ecto 本身创建的 SQL。如果您对 SQL 感兴趣: Overlapping gaps and islands in a school vacation setup

问题

SQL 查询为每个条目提供x 的值,但productx 列为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。

【问题讨论】:

    标签: elixir phoenix-framework ecto


    【解决方案1】:

    在我看来,最好让 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) 并使用现有的 &amp;Repo.load(model, {result.columns, &amp;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)
    

    【讨论】:

      【解决方案2】:

      更好的方法是选择结构所需的所有内容,然后将其移动到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
      

      这种方法的优点是我不使用原始字符串查询。您的计算应该在片段部分内部进行。

      【讨论】:

      • 我不能使用select: %{name: p.name, price: p.price, x: fragment("1")}。这是一个更复杂的 SQL 调用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-11-03
      • 2013-07-06
      • 2013-08-11
      • 1970-01-01
      • 2011-04-15
      • 2018-10-22
      • 2017-10-08
      相关资源
      最近更新 更多