Hi!
I have some old SQL queries that I’d like to migrate to Ecto and I found that I don’t know how to do that The queries use multiple CTEs and use Postgres JSON functions to format the result into a single JSON, so the app just uses the result and doesn’t need to do any post-processing. It looks something like this:
WITH selected_posts AS (SELECT id, type FROM posts WHERE category = $1),
comments AS (SELECT id, text FROM selected_posts sp INNER JOIN comments c
ON c.post_id = sp.id
ORDER BY c.score DESC),
posts_agg AS (SELECT type, count(id) FROM selected_posts GROUP BY type)
SELECT json_build_object(
'posts_aggregated', coalesce((SELECT array_agg(json_build_object(
'type', type
'count', count
)) FROM posts_agg), ARRAY[]::json[]),
'posts_count', SELECT COUNT(id) FROM selected_posts,
'top_comments', coalesce((SELECT array_agg(json_build_object(
'author', author
'text', text
)) FROM comments LIMIT 5), ARRAY[]::json[])
)
The gist is that query selected_posts
is evaluated only once while extracting different information from the query. Note this is not the real thing, it’s only an example to illustrate the approach.
The pure Ecto solution I started with was to have separate query for every field in the response (in this case 3 queries for posts_aggregated
, posts_count
and top_comments
. That works great most of the time, but if the first query, that all other originate from, takes a lot of time and returns lots of rows this work is duplicated in every query. In the CTE-query Postgres could figure this out and only fetch it once (or you could even materialize the CTE manually to enforce this behaviour).
So I wanted to rewrite the query in it’s original form in Ecto, but I am not sure how to write only a select
without from
. E.g. is it possible to write SELECT 1
in Ecto?
The only workaround I found was to use UNNEST(ARRAY[1])
as from, something like the following. But this seems like a hack, is there a better solution?
from(
x in fragment("UNNEST(ARRAY[1])"),
select: %{a: subquery(...), b: subquery(...), c: subquery(...)}
)