How to write complex query with fragments

michaelst57
2023-06-17

michaelst57:

I’m trying to write this query with Ash

select DATE(inserted_at), round(avg(percentage), 2)
from coverage
where "owner" = 'coverbot-io' and "repo" = 'coverbot' and is_for_default_branch
group by 1
order by 1;

I’ve gotten this far but I’m getting an error about fragment/1 being undefined and also not sure how to implement the group by.

Coverage
  |> Ash.Query.filter(owner == ^owner and repo == ^repo and is_for_default_branch)
  |> Ash.Query.select([fragment("DATE(inserted_at)"), fragment("ROUND(AVG(percentage), 2)")])
  |> Ash.Query.sort([1])
  |> Coverbot.Api.read!()

zachdaniel:

Well, you won’t be able to sort by 1 (not sure why you’d want to)

zachdaniel:

And you can’t select fragments

zachdaniel:

You’d need to add a calculation to the resource and select the calculation

zachdaniel:

Or load a one off calculation Ash.Query.calculate

zachdaniel:

I won’t be at a computer for a while but there are examples of expression calcs floating around and in the docs

michaelst57:

The sort by 1 is to sort by the calculated result of the date, here is how I acommplished the query with ecto, maybe this is the better route for this case query = from c in Coverage, join: a in ApiKey, on: c.api_key_id == a.id, select: %{ date: type(fragment("DATE(?)", c.inserted_at), :utc_datetime), percentage: type(fragment("ROUND(AVG(?), 2)", c.percentage), :float) }, where: c.owner == ^owner and c.repo == ^repo and c.is_for_default_branch and a.user_id == ^user.id, order_by: 1, group_by: 1

kernel_io:

make it a sql view?

zachdaniel:

🤔 does order_by: 1 actually do that?

zachdaniel:

TIL about order_by: 1

zachdaniel:

just looked it up, apparently its a thing

zachdaniel:

What exactly are you looking to return?

zachdaniel:

Like thinking about it from a resource perspective, you’ll be accessing this by making some specific query against a resource I imagine

zachdaniel:

The group_by: 1 will also be a problem. Remember the purpose of Ash is to sort of…regulate the way that you interact with these things, not necessarily to contain within it a correlary for every postgres operator.

zachdaniel:

One thing you can potentially do is:

read :whatever_this_is do
  modify_query fn ash_query, ecto_query -> 
    {:ok, new_ecto_query}
  end
end

zachdaniel:

In that query you might be able to accomplish what you want, although it might interrupt other features (like loading aggregates on the returned result)

zachdaniel:

Depending on exactly what you’re looking to do, doing it with ecto might be the best way 🙂

michaelst57:

ya this wouldn’t return any ash resource, it returns an avg coverage percentage by day

michaelst57:

it is to build a graph

zachdaniel:

I’d probably put this as a calculation on a resource somewhere if possible, and then calculate it using ecto.

michaelst57:

Would it make sense to create a virtual resource that isn’t persisted to any datastore?

zachdaniel:

Yes, that would also be an option 🙂 it can add a bit of complexity depending on what features you want to support/how you model it

kernel_io:

yeah you can use numbers for sort and order and group etc

kernel_io:

I’m not a fan of it personally, prefer to be explicit and use names