Search code examples
faunadb

FaunaDB: Query for all documents not referenced by another collection


I'm working on an app where users learn about different patterns of grammar in a language. There are three collections; users and patterns are interrelated by progress, which looks like this:

Create(Collection("progress"), {
  data: {
    userRef: Ref(Collection("users"), userId),
    patternRef: Ref(Collection("patterns"), patternId),
    initiallyLearnedAt: Now(),
    lastReviewedAt: Now(),
    srsLevel: 1
  }
})

I've learned how to do some basic Fauna queries, but now I have a somewhat more complex relational one. I want to write an FQL query (and the required indexes) to retrieve all patterns for which a given user doesn't have progress. That is, everything they haven't learned yet. How would I compose such a query?


Solution

  • One clarifying assumption - a progress document is created when a user starts on a particular pattern and means the user has some progress. For example, if there are ten patterns and a user has started two, there will be two documents for that user in progress.

    If that assumption is valid, your question is "how can we find the other eight?"

    The basic approach is:

    1. Get all available patterns.
    2. Get the patterns a user has worked on.
    3. Select the difference between the two sets.

    1. Get all available patterns.

    This one is trivial with the built-in Documents function in FQL:

    Documents(Collection("patterns"))
    

    2. Get the patterns a user has worked on.

    To get all the patterns a user has worked on, you'll want to create an index over the progress collection, as you've figured out. Your terms are what you want to search on, in this case userRef. Your values are the results you want back, in this case patternRef.

    This looks like the following:

    CreateIndex({
      name: "patterns_by_user",
      source: Collection("progress"),
      terms: [
        { field: ["data", "userRef"] }
      ],
      values: [
        { field: ["data", "patternRef"] }
      ],
      unique: true
    })
    

    Then, to get the set of all the patterns a user has some progress against:

    Match(
      "patterns_by_user",
      Ref(Collections("users"), userId)
    )
    

    3. Select the difference between the two sets

    The FQL function Difference has the following signature:

    Difference( source, diff, ... )
    

    This means you'll want the largest set first, in this case all of the documents from the patterns collection.

    If you reverse the arguments you'll get an empty set, because there are no documents in the set of patterns the user has worked on that are not also in the set of all patterns.

    From the docs, the return value of Difference is:

    When source is a Set Reference, a Set Reference of the items in source that are missing from diff.

    This means you'll need to Paginate over the difference to get the references themselves.

    Paginate(
      Difference(
        Documents(Collection("patterns")),
        Match(
          "patterns_by_user",
          Ref(Collection("users"), userId)
        )
      )
    )
    

    From there, you can do what you need to do with the references. As an example, to retrieve all of the data for each returned pattern:

    Map(
      Paginate(
        Difference(
          Documents(Collection("patterns")),
          Match(
            "patterns_by_user",
            Ref(Collection("users"), userId)
          )
        )
      ),
      Lambda("patternRef", Get(Var("patternRef")))
    )
    

    Consolidated solution

    1. Create the index patterns_by_user as in step two
    2. Query the difference as in step three