la_219

la_219

How do I solve pagination (Repo.aggregate(:count)) on query with group_by in the best way?

Hello all :blush:

Disclaimer: Since this is a work thing, I changed the name of the schemas. The structure is the same.

I’m currently working on a dashboard that shows me all the projects I have. Each project can have x tasks. I work with Ecto.Repo and have an association of has_many in the project schema to map to the tasks. The task schema has the field belongs_to project. That works well, I can add tasks and read them and everything.

On my dashboard I want to have an overview of all my active projects and the count of tasks. So basically a list with

  • my first project (5 tasks)
  • my second project (2 tasks)

So far so good. My code looks like this

      Project
      |> select([p, t], %{
        id: p.id,
        title: p.title,
        state: p.state,
        task_count: count(t.id),
      })
      |> join(:left, [p], t in assoc(p, :tasks))
      |> where(^filter_by(filter))
      |> group_by([p], p.id)
      |> order_by(asc: :title)
      |> Repo.all()

The filter_by is a dynamic function that allows me to filter by term, state (and more to come). That works so I will skip the code for that. I can include it, if you feel it is necessary.

Now I want to paginate everything. So far, I’ve written a generic module to do that for me (because I will need it all the time for other stuff). So in the above code, I want to use that instead of Repo.all(). The call looks like this

[... all the query stuff from above except for the Repo.all() ]
|> Pagination.page(%{number: page_number, limit: page_limit}) 

And my module looks like this:

defmodule Infrastrucutre.Repo.Pagination do
  import Ecto.Query

def page(query, %{number: number, limit: page_limit}) do
    query
    |> limit([r], ^page_limit)
    |> offset([r], ^((number - 1) * page_limit))

    %{
      page: Repo.all(query),
      stats: %{
        count: count(query, %{page_limit: page_limit}),
        number: number
      }
    }
  end

  defp count(query, %{page_limit: page_limit}) do
    ceil(Repo.aggregate(query, :count) / page_limit)
  end
end

The alert reader will probably already see the problem. While this module worked fine for me so far, I am now for the first time trying to aggregate a query that already has an aggregation (= count(t.id)) in it. And that does not work at all because I can’t aggregate an aggregation. The excat problem being

** (Ecto.QueryError) cannot aggregate on query with group_by in query:

To solve this, I can have two queries in my repository, one for all my projects that I hand to the method count (I would have to make this public then) that gives me the count. And one for all my projects including the task count that I return in the pagination as “page”. Then I would prepare my pagination in my repository and return that.

Or I can have two methods in pagination. One with the limit and offset and one without where I can use Repo.all on and then get the length of all the entries without the limit and offset.

Both do not feel like clean code. So I am wondering if there is another way to do it that I haven’t thought of yet. What’s the most elixir way to do it?

Thanks for your time and input! Have a great day :sunny:

Marked As Solved

JohnnyCurran

JohnnyCurran

Could you use exclude? Ecto.Query — Ecto v3.14.0

This will let you reset the :group_by in your query (and other fields) and you can then hopefully get the count

I have only just skimmed your post and this might not be applicable to your situation. You will still need to make two queries but maybe not two functions. You could maybe do:

{with_count, without_count} = {Repo.all(exclude(query, :group_by)), Repo.all(query)}

Or something like that

Also Liked

la_219

la_219

Oh wow, I did not know about that function. Thank you very much, it worked instantly! :smile:

Last Post!

kayvon

kayvon

I ran across this issue as well and decided to wrap my query in a subquery to be able to get the count. That seemed to work as well.

Where Next?

Popular in Questions Top

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
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Other popular topics Top

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
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31494 112
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 40042 209
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? 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

We're in Beta

About us Mission Statement