简体   繁体   中英

In Elixir with Postgres, how can I have the database return the enum values which are NOT in use?

I have an EctoEnum.Postgres:

# @see: https://en.wikipedia.org/wiki/ISO_4217
defmodule PricingEngine.Pricing.CurrencyEnum do
  @options [
    :AED,
    :AFN,
    # snip...
    :ZWL
  ]
  use EctoEnum.Postgres,
    type: :currency,
    enums: @options

  def values, do: @options
end

This enum has been included in our Postgres database

We also have a structure:

defmodule PricingEngine.Pricing.Currency do
  use Ecto.Schema
  import Ecto.Changeset

  schema "currencies" do
    field(:currency, PricingEngine.Pricing.CurrencyEnum)

    timestamps()
  end

  @doc false
  def changeset(currency, attrs) do
    currency
    |> cast(attrs, [:currency])
    |> validate_required([:currency])
    |> unique_constraint(:currency)
  end
end

We can currently successfully use the following functions to figure out which currencies are active/used:

  def active_currency_isos do
    Repo.all(select(Currency, [record], record.currency))
  end

  defdelegate all_currency_isos,
    to: CurrencyEnum,
    as: :values

  def inactive_currency_iso do
    Pricing.all_currency_isos() -- Pricing.active_currency_isos()
  end

This works, but I'm led to believe this could be more efficient if we just asked the database for this information.

Any idea(s) how to do this?

If you want to get a list of all the used enums you should just do a distinct on the currency field. This uses the Postgres DISTINCT ON operator:

from(c in Currency,
 distinct: c.currency,
 select: c.currency
)

This will query the table, unique by the currency column, and return only the currency column values. You should get an array of all of the enums that exist in the table.

There are some efficiency concerns with doing it this way which could be mitigated by materialized views, lookup tables, in-memory cache etc. However, if your data set isn't extremely large, you should be able to use this for a while.

Edit:

Per the response, I will show how to get the unused enums.

There are 2 ways to do this.

Pure SQL

This query will get all of the used ones and do a difference from the entire set of available enums. The operator we use to do this is EXCEPT and you can get a list of all available enums with enum_range . I will use unnest to turn the array of enumerated types into individual rows:

SELECT unnest(enum_range(NULL::currency)) AS unused_enums
EXCEPT (
    SELECT DISTINCT ON (c.name) c.name
    FROM currencies c
)

You can execute this raw SQL in Ecto by doing this:

Ecto.Adapters.SQL.query!(MyApp.Repo, "SELECT unnest(...", [])

From this you'll get a Postgresx.Result that you'll have to get the values out of:

result
|> Map.get(:rows, [])
|> List.flatten()
|> Enum.map(&String.to_existing_atom/1)

I'm not really sure of a way to code this query up in pure Ecto, but let me know if you figure it out.

In Code

You can do the first query that I posted before with distinct then do a difference in the code.

query = from(c in Currency,
  distinct: c.currency,
  select: c.currency
)

CurrencyEnum.__enums__() -- Repo.all(query)

Either way is probably negligible in terms of performance so it's up to you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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