“Join” for FQL v10
The simplest “join” is more like a transformation, swapping some data for different data. You can use the .map()
method to do this (set.map() - Fauna Docs)
Fetch users who are organizing events this week
Event.all_by_date({ from: Date.today(), to: Date.today().add(7, "days"))
.map(e => e.owner) {
id,
name,
}
We already talked about .flatMap()
, too, which is useful for joining one-to-many and many-to-many relationships.
For example, if you want to get all the attendees for events happening this month, you can call an additional index within .flatMap
.
fetch users who are attending events this week
Event.all_by_date({ from: Date.today(), to: Date.today().add(7, "days") })
.flatMap(e => Attendees.by_event(e)) {
id,
name,
// You can embed data in each item like a traditional SQL JOIN if you would like
attending_event: e { id, name, date }
}
You don’t have to flatten everything, though, if you want all the related data. With a document structure, it can be nice to nest data instead. That would use map
and return embedded Sets.
Event.all_by_date({ from: Date.today(), to: Date.today().add(7, "days") })
.map(e => e {
id,
name,
date,
attendees: Attendees.by_event(e) { id, name }
})
The big difference between the last two examples is that using flatMap
to handle a single Set allows you to execute a single paginated query. In the latter example, if there are more than pageSize
events, then you have to paginate those, and if any of the embedded Sets for attendees are also too big, then each one of those would require additional requests.
“Intersection” (“AND”) for FQL v10
An intersection identifies documents that exist in each provided Set. However, calculating an intersection directly from multiple Sets is problematic since it requires resolving the entirety of each Set before returning any results. This causes big problems once Sets start getting bigger.
To accomplish the task with v10, you should try to use the one most selective index available for your query and then filter those results using where
. You may use additional indexes in the filter predicate, or other calculations (ideally on values already covered by the index).
For example, if you want to search for Events this week that are also recurring you can use the by_date
index and then filter. No need to mash two indexes together.
let me = Query.identity()
Event
.by_date(me, { from: Date.today(), to: Date.today().add(7, "days") })
.where(.recurring)
This would be more efficient if the .recurring
field was included in the values for by_date
index.
If you are trying to intersect with relationships, then you can use additional indexes to check for existence
Check if any of my events this week do not have attendees
Event
.by_date(me, { from: Date.today(), to: Date.today().add(7, "days") })
.where(e => Attendees.by_event(e).take(1).isEmpty()) // `take` here ensures Fauna doesn't try to read more than necessary just to check for existence.
The most literal translation of Intersection would be reading each set and checking if the value is included, but it requires scanning entire Sets. This is indeed how FQL v4 Intersection
worked. It works fine for small Sets, but does not scale well.
Get users who are attending each of these events
let attendees1 = Attendees.by_event(e1)
let attendees2 = Attendees.by_event(e2)
let attendees3 = Attendees.by_event(e3)
attendees1.
.where(a => attendees2.includes(a)) // requires full scan of theSet
.where(a => attendees3.includes(a)) // requires full scan of theSet