Sanjibukai

Sanjibukai

How to apply SQL array aggregation function in Ecto

Hi everybody,

Edit for TL;DR:
How I can use SQL array_agg aggregate function with an Ecto fragment.
Particularly how I can nest the selects..

Let me start with some little explanations of my problem (because it might be irrelevant in real world use case..)

Say I have Posts and Users and I want to fetch all users and also all posts associated to each user.
The association I have is only defined in the Post schema with a belongs_to :user, User
So the following give me what I want but with the User duplicated in every row:

iex> Repo.all from p in Post,
         join: u in assoc(p, :user),
         select: {u.name, p.title}
[
  {"José", "Post 1"},
  {"José", "Post 2"},
  {"Joe", "Post A"},
  {"Joe", "post B"}
]

Now what I’m wanting is to get the following:

[
  {"José", ["Post 1", "Post 2"]},
  {"Joe", ["Post A", "Post B"]}
]

I can achieve this result using Enum.reduce with a Map.update in Elixir side:

Enum.reduce(map, %{}, fn {k, v}, acc -> Map.update(acc, k, [v], &[v|&1]) end)

All of the above are examples using select and ending with results having basic typed values (like strings here). And in fact name here is to be considered unique or maybe adding the id as a key, but the point here is that I can always achieve what I want in Elixir side.

And I also know that having has_many :post on the User Schema and then using preload could do the trick but only with having the elixir Structs. I mean I didn’t managed to use select as above to just grab the user name and the video title.

Note: I know that this question might seem to mix concerns like domain and views.
In real world scenario, I would have loaded the Structs and then grab according fields within a view, etc..
But I’m asking for learning purpose.

So I wanted to know if it’s somehow possible to achieve that using more elaborated SQL queries..
I tried many naive code combinations without success..
For example, I tried to use group_by: :user but then I got an error because of the lack of aggregating function.
Then I discovered array_agg aggregate function in SQL (and postgres). I guess I need to use it somehow with fragment but I don’t have any clue how to use it (besides simple example I saw in the documentation)?

So does anyone can give me some hints..

Marked As Solved

hauleth

hauleth

from p in Post,
  join: u in assoc(p, :user),
  group_by: u.name,
  select: {u.name, fragment("array_agg(?)", p.title)}

You can clean it a little by using macro:

defmacro array_agg(field) do
  quote do: fragment("array_agg(?)", unquote(field))

from p in Post,
  join: u in assoc(p, :user),
  group_by: u.name,
  select: {u.name, array_agg(p.title)}

Or if you use many of such custom functions then I have created ecto_function which provides helper functions for defining such macros. So the above will be:

defqueryfunc array_agg(field)

from p in Post,
  join: u in assoc(p, :user),
  group_by: u.name,
  select: {u.name, array_agg(p.title)}
16
Post #2

Also Liked

Sanjibukai

Sanjibukai

Thanks for your clear answer. I appreciated how you gradually put things, finishing by presenting your package.

I took a look and it’s definitely a good idea!

rameramwe

rameramwe

Thank you very much! i was stuck for multiple hours on this :smiley:
You’re awesome!

Where Next?

Popular in Questions Top

Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
earth10
Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone. What strikes me is th...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lists...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
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
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

Other popular topics Top

skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31142 143
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement