BitGonzo
I’d love to see how others are building such queries. From my research, I haven’t found a single thread that comes of anything. Is it that rare of a problem?
My current issue is I need to fetch (10) records from one table, and limit (5) the preloaded associated records in another table. I also imagine in the future I’ll need to be able to offset and order records in either table.
Can I do this with subqueries? If so, how do I go about mapping this to structs?
The only solution I can see right now is to build it using subqueries, and then manually assign the struct mappings for the results of the subquery and forget about Ecto preload.
https://github.com/elixir-ecto/ecto/issues/1956
https://github.com/elixir-ecto/ecto/issues/1438
I feel like I’m really close:
Repo.all(
from c in query,
left_join: rr in fragment("select * from my_table limit 10"),
on: rr.c_id == c.id,
preload: [records: rr],
limit: 1
)
However, doesn’t seem possible with preload:
Bug Bug ..!!** (Ecto.QueryError) can only preload sources with a schema (fragments, binary and subqueries are not supported) in query:
Can I not just assign the preloaded associations to each of my structs myself, or is the problem with that one of the reasons this isn’t implemented?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










Showing Posts 1 to 8- Show Best Posts
- Show All Posts (oldest first)
- Show All Posts (newest first)
seanwash
I’m sorry about reviving an old thread, but did you ever find a solution to this? I’m needing to do something very similar.
peerreynders
You can limit the number of associations with a preload query.
Another example.
seanwash
I was doing something very similar to this and got different results. I’ll recheck though! Thanks for replying.
benwilson512
That won’t work for this, which is the whole problem. If you
limit: 10in a preload query you’ll get back 10 records total not 10 records per item, which is what we want. The only ways I know to get the 10 records per item is via lateral joins or window functions. lateral joins have limited support in ecto, window functions have no support.BitGonzo
I wish I could say I found a streamlined way of achieving this.. but I really didn’t.
I had to:
Repo.query!(query)r_my_column, etc) to avoid conflicts (id, etc)Repo.loadLot of ceremony, but this does work and gives me back exactly what I need. I’m only 3 months into Elixir so if I’m doing something naive and this can be simplified I’m very open to suggestions. It was written as part experiment, part requirement, so am not settled yet.
Considering this is in fact possible as seen below, I’m not sure why Ecto isn’t providing a way. I wish I could contribute, but I’m probably a good 6+ months from being able to contribute anything meaningful.
I actually prefer writing custom queries as can fine-tune them to my needs and know exactly what is being executed. I just need a simple way of parsing generic result sets… and I guess that is one of the challenges and potentially why Ecto doesn’t currently support this.
(selects on query have been simplified for illustration purposes)
peerreynders
In this particular case a
fragment/1is only really necessary for the right side of the:lateral_join. One advantage of doing that is that you can push some of the work back to Ecto.Example:
Full Example:
peerreynders
Breaking it down:
This query simply selects the
%MusicDB.Artist{}of the most recently inserted artists. The intent is to use it for a subquery inThis part of the query contains the lateral join to identify the two most recently added albums to the artists specified by the subquery. Because of the
fragmentthis information can’t be correlated to the schemas known to Ecto.This join gets around that issue by joining against the
albumstable again - this time in a fashion that lets Ecto use%MusicDB.Album{}and because we want to use this query as a subquery we have to use an Ecto struct (list, maps, tuple aren’t supported for subqueries) which is accomplished withselect: al(without it, only the%MusicDB.Artist{}would be returned). This means that in this pass we lose%MusicDB.Artist{}but as%MusicDB.Album{}hasartist_id(and more importantlybelongs_to(:artist, Artist)) we can get that back later.Now given that we have all the albums we want, we also want the child
tracksand the parentartist. This query does this via Ecto’sEcto.Query.preload/3to load them as associations (strictly an Ecto feature, not SQL) withpreload: [:tracks, :artist].order_by: [asc: :artist_id, asc: :inserted_at]keeps all the%MusicDB.Album{}for the same artists “together” while ordering them in ascending order. This ordering will be reversed (i.e. descending) during “post load processing” which is the final ordering we are looking for.Using the query now, Ecto will generate 3 separate SQL queries:
%MusicDB.Album{}values%MusicDB.Artist{}preload values%MusicDB.Track{}preload valuesHowever the resulting shape of the data is like this:
but the shape we are looking for is
So some data transformation is still required
forget_artist/1is used to “unload” theartistassociation from%MusicDB.Album{}when we are done with it.make_result/1processes the loadedrowsto produce the desired data shape. The accumulating data structure is{artist, [album], [artist_with_albums]}whereartistis the%MusicDB.Artist{}currently being worked on,[album]is the list of%MusicDB.Albums{}for the currentartist, and[artist_with_albums]is the list of already processed%MusicDB.Artist{}structs. The finalcompact_to_rest/1is necessary to move the lastartistand its[album]into[artist_with_albums].Support functions for
make_result/1:compact_to_rest1:artistand its[album]into[artist_with_albums]and returns the new resulting[artist_with_albums]. OR[artist_with_albums]if there is noartist(and[album]).row_to_acc/2:MusicDB.Album{}to[album]if itsartistmatches the currentartist(after “unloading” the struct’sartistassociation). ORartistand[album]into[album_with_artists], before setting the new “current”artist/[album]toalbum.artist/[forget_artist.(album)].Note: this approach will lose artists without any albums. Those could be recovered with:
PS: Conceptually I’m still not entirely sold on whether it actually makes any sense to put anything but the complete set of associations inside an Ecto association given that it is an Ecto concept rather than something that emerges naturally from SQL.
While there may be practical reasons for wanting only some constrained subset of the complete set of associations:
So whenever something like
LIMITcomes into play it may make more sense to avoid Ecto associations altogether and simply stick toEcto(.Query)'s plain query functionality and then massage the loaded rows into the required shape via data transformations.BitGonzo
Hey, just wanted to pop in and say thank you very much for breaking this down!
I’ve not worked on the application for a few weeks so not had time to modify much. I’ll refactor once I’ve digested this excellent response. Appreciate it.