I know not to worry about performance before getting evidence of a problem, but I’m idly curious about preloads. (Note: I have fairly little experience with databases.)
Correct. Keep in mind that this is often the best way to load one to many associations because of the way SQL database return data. If you have three tables, all associated in one to many, and each table returns respectively K, M, N entries, you will get overall K * M * N rows. That ends up with a lot of duplication, which translates to more data over the wire and more work decoding the data. When using preloads, you get K + M + N rows, which ends up being more performant.
If you want to force to actually go the join route, you can do so too:
from q in query, join: s in assoc(q, :species), join: sg in assoc(q, :service_gaps), preload: [species: s, service_gaps: sg]