We have the need to use an INSERT query with the source being a subquery. I'd love to do this in ecto DSL since I need to include a few parameters dynamically.
Right now, it's possible to insert specific fields using subqueries for each field, but that wouldn't work in our case since we want to upsert an aggregation like
select device_id, t.key as data_key, jsonb_typeof(t.value) as data_type, count(*) as readings_count, min(r.measured_at) as oldest_reading, max(r.measured_at) as newest_reading, now() as last_update from readings r
join lateral (select * from jsonb_each(r.data)) t on true
where measured_at > now() - '1 hour'::interval
group by 1, 2, 3)
While I can express that subquery in Ecto DSL, I can't give it to Repo.insert_all. For the conflicts, it's awesome that supplying an Ecto.Query as on_conflict option is already possible. If it were possible to formulate an INSERT query using just queries and no data it would be even better.
I imagine the call working something like this:
query = from s in StatSource,
select: %{
count: count(s),
oldest: min(s.inserted_at),
foobar: s.foobar
},
group_by: s.foobar
conflict_query = from s in MyStatisticSchema,
update: [set: [
count: fragment("EXCLUDED.count + ?", s.count),
oldest: fragment("least(EXCLUDED.oldest, ?", s.oldest)
]]
Repo.insert_all(MyStatisticSchema, query,
conflict_target: [...], #fields from the given query
on_conflict: conflict_query
)
which would get transformed to this SQL query:
INSERT INTO statistics (
SELECT COUNT(s) AS count, MIN(s.inserted_at) AS oldest, s.foobar FROM source
GROUP BY s.foobar
)
ON CONFLICT (foobar) DO UPDATE SET
count = EXCLUDED.count + statistics.count, oldest = LEAST(EXCLUDED.oldest, statistics.oldest)
I hope this is clear enough. For now I will have to use raw SQL in my code.