繁体   English   中英

在SQL连接查询期间获取Ecto中的虚拟字段的计算结果

[英]Getting a virtual field in Ecto calculated during a SQL Join Query

这可能更多是SQL问题,而不是Elixir / Etco问题。

我与User-Transactions-Merchant有很多关系,其中用户通过交易拥有许多商家,而商人通过交易拥有许多客户。 那是很典型的。 通过执行以下操作,我可以通过Ecto吸引所有商人的客户:

def find_merchant_customers(%{"merchant_id" => id}) do
  merchant = Repo.get(Merchant, id)
  Repo.all assoc(merchant, :customers)
end

如果我想为具有特定商家的用户找到余额,则可以使用类似这样的SQL查询来总结交易并为该商家产生余额。

def customer_balance(user_id: user_id, merchant_id: merchant_id) do
  q = from t in Transaction,
    select: fragment("SUM(CASE WHEN ? = 'credit' THEN (?) ELSE - (?) END)", t.type, t.amount, t.amount),
    where: t.user_id == ^user_id and t.merchant_id == ^merchant_id
  balance = Repo.one(q) || 0
  do_balance(balance, "asset")
   |> Money.new(:USD)
end

问题

如何将这两个操作组合到一个查询中,以使Join检索用户列表并在用户中填充Balance的虚拟属性。 我知道我可以只运行第一个查询并获取用户列表,然后通过检索每个用户的每个余额来转换数据,尽管这似乎效率很低。 一种替代方法可能是了解如何将select fragement(分配给查询中的属性作为子查询。任何指导都将有所帮助。

我的用户模型

defmodule MyApp.User do
  @moduledoc """
  User struct for user related data
  """
  import MyApp.Validation
  use MyApp.Model
  use Coherence.Schema

  schema "my_app_users" do
    field :email, :string
    field :first_name, :string
    field :last_name, :string
    field :role, :integer
    field :birthdate, Ecto.Date
    field :address1, :string
    field :address2, :string
    field :city, :string
    field :state, :string
    field :zip, :string
    field :status, :boolean, default: true
    field :verified_email, :boolean, default: false
    field :verified_phone, :boolean, default: false
    field :mobile, :string
    field :card, :string
    field :sms_code, :string
    field :balance, Money.Ecto, virtual: true
    field :points, :integer, virtual: true

    coherence_schema

    has_many :transactions, MyApp.Transaction
    has_many :merchants, through: [:transactions, :merchant]

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:email, :first_name, :last_name, :password_hash, :role, :birthdate, :address1, :address2, :city, :state, :zip, :status, :mobile, :card, :sms_code, :status, :merchant_id, :verified_email, :verified_phone])
    |> validate_required_inclusion([:email, :mobile])
    |> validate_format(:email, ~r/(\w+)@([\w.]+)/)
  end

  defp put_password_hash(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true, changes: %{password: password}} ->
        put_change(changeset, :password_hash, Comeonin.Bcrypt.hashpwsalt(password))
      _ ->
        changeset
    end
  end

我的商家模型

defmodule MyApp.Merchant do
  @moduledoc """
  Merchant Struct

  Merchant has an owner of a User - Which must exist
  """
  use MyApp.Model
  use Arc.Ecto.Schema

  schema "my_app_merchants" do
    field :name, :string
    field :email, :string
    field :address1, :string
    field :address2, :string
    field :city, :string
    field :zip, :string
    field :state, :string
    field :status, :boolean, default: true
    field :description, :string
    field :image, MyRewards.Avatar.Type
    field :phone, :string
    field :website, :string

    has_many :transactions, MyApp.Transaction
    has_many :customers, through: [:transactions, :user]

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name, :email, :address1, :address2, :city, :zip, :state, :status, :description, :phone, :website, :status, :category_id, :user_id])
    |> cast_attachments(params, [:image])
    |> validate_required([:name])
    |> validate_format(:email, ~r/(\w+)@([\w.]+)/)
  end
end

查询功能

  def find_merchant_customers(%{"merchant_id" => id}) do
    merchant = Repo.get(Merchant, id)
    Repo.all assoc(merchant, :customers)
  end


  def customer_balance(user_id: user_id, merchant_id: merchant_id) do
    q = from t in Transaction,
        select: fragment("SUM(CASE WHEN ? = 'credit' THEN (?) ELSE - (?) END)", t.type, t.amount, t.amount),
        where: t.user_id == ^user_id and t.merchant_id == ^merchant_id
    balance = Repo.one(q) || 0
    do_balance(balance, "asset")
      |> Money.new(:USD)
  end

将片段移到宏中以保持代码清晰:

  defmacro balance_amount(transaction) do
    quote do
      fragment("CASE WHEN ? = 'credit' THEN (?) ELSE - (?) END",
        unquote(transaction).type, unquote(transaction).amount, unquote(transaction).amount)
    end
  end

使用%{user_id,merchant_id,余额}创建子查询

  def user_merchant_balance do
    from t in Transaction,
    select: %{user_id: t.user_id, merchant_id: t.merchant_id, balance: sum(balance_amount(t))},
    group_by: [t.user_id, t.merchant_id]
  end

从主查询加入子查询,使用地图更新语法%{|}填充虚拟字段:

  def merchant_customers(merchant_id) do
    from u in User,
    join: b in subquery(user_merchant_balance()), on: u.id == b.user_id,
    where: b.merchant_id == ^merchant_id,
    select: %{u | balance: b.balance}
  end

编辑:在Ecto 2.2中, balance字段可以Money.Ecto.TypeMoney.Ecto.Type

  def merchant_customers(merchant_id) do
    from u in User,
    join: b in subquery(user_merchant_balance()), on: u.id == b.user_id,
    where: b.merchant_id == ^merchant_id,
    select: %{u | balance: type(b.balance, Money.Ecto.Type)}
  end

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM