gutomotta

gutomotta

Ecto: how to insert into a Schema with one field being a subquery

Hey everyone, just joined this forum. I’m new to Elixir and have been working on a side project to learn it. Happy to be here.

I have a question related to Ecto, and I’m not sure if what I’m trying to do makes sense.

I want to insert rows in a SQL table from an Ecto.Changeset that will contain some fields provided by the user, and I want one of the fields not to be provided by the user, but to be the result of a query instead.

Some Background

I’m writing a Phoenix application. It has a SQL database (I’m using SQLite), and there are two related Schemas: a “Parent” has_many “Children” (so “Child” belongs_to “Parent”, it has a parent_id FK).

I want the children table to have a column named parent_scoped_row_id, which would store the row number of that child scoped by parent_id. Here’s an example of what I mean:

id parent_id inserted_on parent_scoped_row_id
1 1 2023-12-01 1
2 1 2023-12-02 2
3 1 2023-12-03 3
4 2 2023-12-01 1
5 2 2023-12-02 2
6 3 2023-12-01 1

I want parent_scoped_row_id to not be provided by users, but auto-generated instead.

SQL solution

The following works on SQLite:

create table parents(id primary key);
create table children(
  id primary key,
  parent_id integer not null constraint "child_parent_fk" references parents(id),
  inserted_on text,
  parent_scoped_row_id integer
);
insert into parents values (1), (2), (3);
insert into children values (
  1,
  1,
  "2023-12-01",
  (select 1 + coalesce(max(parent_scoped_row_id), 0) from children where parent_id = 1)
);
insert into children values (
  2,
  2,
  "2023-12-01",
  (select 1 + coalesce(max(parent_scoped_row_id), 0) from children where parent_id = 2)
);
-- and so on...

Problem is: how can I reproduce it with Ecto?

What I’ve tried

# child.ex
defmodule Myapp.Example.Child do
  use Ecto.Schema
  import Ecto.Changeset

  alias Myapp.Example.Parent

  schema "children" do
    field :inserted_on, :date
    field :parent_scoped_row_id, :integer

    belongs_to :parent, Parent
  end

  @doc false
  def changeset(child, attrs) do
    child
    |> cast(attrs, [:inserted_on, :parent_id])
    |> validate_required([:inserted_on, :parent_id])
  end
end
# application code
defmodule Example
  def create_child(attrs \\ %{}) do
    changeset =
      %Child{},
      |> Child.changeset(attrs)

    parent_id = get_field(changeset, :parent_id)

    subquery =
      from(
        c in Child,
        where: c.parent_id == ^parent_id,
        select: 1 + coalesce(max(c.parent_scoped_row_id), 0)
      )

    changeset
    |> put_change(:parent_scoped_row_id, subquery)
    |> Repo.insert()
  end
end

But when I run create_child(), I get this error:

** (Ecto.ChangeError) value `#Ecto.Query<from c0 in Myapp.Example.Child, where: c0.parent_id == ^1, select: 1 + coalesce(max(c0.parent_scoped_row_id), 0)>` for `Myapp.Example.Child.parent_scoped_row_id` in `insert` does not match type :integer

I also tried using other types for :parent_scoped_row_id, and also not declaring it altogether but none of those worked.

I could probably solve this by getting the field and then putting it in the changeset before inserting, all inside a transaction. Not sure this would work, but I really wanted to try solving it with a single SQL statement before going this path.

What now?

I’m not sure where to go from here. I suppose I could try to build a Custom Field Type instead of :integer for my field, or try Ecto’s autogenerate, or something else. But as I said, I’m new to all of this and I don’t know how autogenerate and Custom Field Types work. I also don’t know if those are the right solution for the problem.

Does anyone have any insights that could help me?

Thank you for your patience reading all this :sweat_smile:

Marked As Solved

joey_the_snake

joey_the_snake

It seems like the SQLite adapter does not handle query values at the moment. It is turning it into a query parameter. Take a look at the difference between the Postgres and SQLite adapter for this function:

SQLite: ecto_sqlite3/lib/ecto/adapters/sqlite3/connection.ex at main · elixir-sqlite/ecto_sqlite3 · GitHub

Postgres: ecto_sql/lib/ecto/adapters/postgres/connection.ex at master · elixir-ecto/ecto_sql · GitHub

My advice would be to open an issue for the sqlite adapter and see if it’s possible for them to support it.

Also Liked

joey_the_snake

joey_the_snake

insert_all will allow fields to be populated by a subquery

joey_the_snake

joey_the_snake

No problem. The issue with plain insert is that it expects values so it can perform validation prior to persisting to the database.

Where Next?

Popular in Questions Top

nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: The documentation above suggests that while ...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 127089 1222
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement