Union + current syntax

In the previous FQL there was a Union operator, but I cannot find something similar using the current FQL syntax.

About the query, I want to find any documents that have a date within a specific range OR that have a boolean value, recurring, set.

Any other solutions besides a Union operator would be great too.

Event.by_date(User.byId(${parsedSession.id}), {
    from: ${from},
    to: ${to}
})

// Union with

Event.recurring(User.byId(${parsedSession.id}), true)

schema

Given a schema like this

collection Event {
  index by_date {
    terms [.owner]
    values [.date]
  }
  index recurring {
    terms [.owner, .recurring]
    values [.date]
  }
  owner: Ref<User>
  moments: { createdAt: Time, lastUpdatedAt: Time }
  name: String
  notes: String?
  date: String
  recurring: Boolean
}

You can query multiple Sets at once with FQL v10 using either

Which one you use depends on which one is more convenient/ergonomic for your use case. They will both perform similarly when used this way.

I’ll cover other Set combinations as well. We have a task to add more information around this in the documentation. We’d love more feedback!

“Union” (“OR”) for FQL v10

Using concat()

The simplest way to get the results of multiple Sets is to use the .concat() method.

let set1 = Event.by_date(User.byId(${parsedSession.id}), {
    from: ${from},
    to: ${to}
})

let set2 = Event.recurring(${parsedSession.id}, true)

set1.concat(set2)

Tada! :tada: :slight_smile:

You can chain many Sets together as you like

Stuff.byColor("red")
  .concat(Stuff.byColor("white"))
  .concat(Stuff.byColor("blue"))

Using flatMap()

<Set>.flatMap() allows you to iterate over a items in a Set, call a function on that item that returns a new Set for each item, then flattens all of the results.

This can help readability when you have list of Sets to combine, or you require some compute to build up a list of Sets. It is less useful when you have a list of different index, but very handy when you want to reuse the same logic.

Take my Stuff.byColor() example from above. I could do this instead:

["red", "white", "blue"]
  .toSet() // Has to be a Set for .flatMap to work with function that returns a Set
  .flatMap(color => Stuff.byColor(color))
1 Like

“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
1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.