Retrieve average score by related ID and other beginner queries

Hello Fauna :wave:

I’ve set up a small project in order to discover Fauna, that I thought would be simple enough to get started but I think I’m missing some understanding over core Fauna concepts to get going.

What I’m trying to achieve

A web app where users can submit intro lines they use on dating sites. Other users see messages they haven’t scored yet (a mix of new and top lines) and can swipe them left or right. Once they do, they see the percentage note that was given by other users. There is also a simple screen where you see top, worst and trending lines.

My model

Here is the model I wrote, stripped down from inessential values. Eval score integer can be set to 1 or -1, so that using Mean on the list of documents would return a 0 to 1 value that can be used to display the average score of the intro line (no idea how I would handle this for thousands of documents but I guess it’s not my biggest problem atm).

type Eval {
  submission: Submission! @relation(name: "submission_evals")
  from: User! @relation(name: "user_evals")
  score: Int!
}

type Submission {
  created_at: Date!
  status: SubmissionStatus!
  payload: String!
  from: User
  evals: [Eval!] @relation(name: "submission_evals")
}

type User {
  evals: [Eval!] @relation(name: "user_evals")
  submissions: [Submission!] @relation(name: "user_submissions")
}

type Query {
  submissionsByStatus(status: SubmissionStatus!): [Submission]!
  submissionAverageScore(id: ID!): Float! @resolver(name: "query_submission_average_score")
}

My (failing) queries

1/ Compute average score of evals by submission ID

I’ve tried to create and use indexes to query the database the way I need. Note that I have no real prior experience with SQL, so my understanding of index settings is pretty thin ATM.

So, once I created some submissions and evals via the Graphql playground, I tried first to query all scores by submission ID.

The index:

This allows me to fetch scores by submission ID:

Mean(Paginate(
  Match(
    Index("submission_evals_by_submission"),
    Ref(Collection("Submission"), "285368197663687173")
  )
))

I then created a function called query_submission_average_score with Server role, that looks like this:

Query(
  Lambda(
    "ID",
    Mean(
      Paginate(
        Match(
          Index("submission_evals_by_submission"),
          Ref(Collection("Submission"), Var("ID"))
        )
      )
    )
  )
)

That I should be able to call via my submissionAverageScore Graphql query. But when I do, I get a String or Number expected, Array provided. And I’m pretty much stuck at that point for that specific query :blush:

2/ query by absence of relationship

In order to show users a list of Submissions they did not rate yet, I should be able to query a somewhat random list of submissions they both did not rate nor submit. This is where things are getting a bit too abstract to me.

From my understanding, I should create an index on Submission table with return values from property evals. Which I did. But when I tried to simply display the list of evaluations found for each submission:

Map( // Should be Filter in real life
  Paginate(Match(Index("submission_data_evals"))),
  Lambda(
    "subRef",
    Let(
      { data: Get(Var("subRef")) },
      {
        evals: Select(["data", "evals"], Var("data"))
      }
    )
  )
)

But it seems nothing is returned from that call. I’m curious what’s the correct way to achieve that? I think I’m looking for a Not(Intersection()) between an index of user already submitted Eval submission ref and a list of refs of actual Submissions but I have zero clue on how to achieve that

3/ Create Eval only if no relationship exists already

An other problem I encounter is that I have to check on my side if a user has already rated a Submission or before allowing them to rate it. From reading the documentation, I understand that FQL probably allows that but I’m super unsure on how to proceed. I saw that If and Exists is available but they only allow to check that a specific ref exists. I thought of that pseudo FQL function:

Lambda(
  If (Not Exists a Get on Eval with ID Var("id") and data.from Var("userId)),
  Create(Collection("Eval"), {
    data: {
      score: Var("score"),
      from: { connect: Var("userId") },
      submission: { connect: Var("submissionId") },
    }
  }),
  Abort("Submission was rated by user already")
)

For this, I created an index that with search terms data.submission and data.from and tried to query the document first:

Paginate(
  Match(Index("eval_search_by_submission_and_user"), [
    "285795526219137541",
    "285795526199214597"
  ])
)

But nothing is returned.

Conclusion

If anyone could help with one or several of these queries, I’d be more than happy to read them. I know my post is quite long but I tried to provide as many info as possible. Maybe that’s of interest for you to see how a front-end developer with minor experience in backend work can get stuck somewhere on their learning curve :blush:

Happy holidays

@hypervillain

I think this is because Mean returns an array and GraphQL API expects Float!. Try this

Query(
  Lambda(
    "ID",
    Select(['data',0],Mean(
      Paginate(
        Match(
          Index("submission_evals_by_submission"),
          Ref(Collection("Submission"), Var("ID"))
        )
      )
    ))
  )
)

To summarize, you have a User who can submit multiple Submission (one-to-many). Each of those Submission has a status and multiple Evals (one-to-many) created by multiple users. Right?

One way of getting this list of submissions they both did not rate nor submit. is to use something like below. (Check for syntax)

Union(
  Match(Index('submission_by_status'),'Not Submitted'),
  Difference(Documents(Collection('Submission')),Match(Index('submission_by_user'),'current user'))
)

For the third issue,

You can use ID on GraphQL API, but on the FQL side, it should be a Ref.

Paginate(
  Match(Index("eval_search_by_submission_and_user"), [
    Ref(Collection('Submission'),"285795526219137541"),
    Ref(Collection('User'),"285795526199214597")
  ])
)

Hope this helps.

1 Like

Thanks a ton for your answer @Jay-Fauna, I’ve been carefully reading it + Fauna documentations and my app is a lot more advanced now :v:I can see how great FQL is once you bother learning…

So, I’ve been able to create an Eval if it doesn’t exist already and return its average score after that:

Query(
  Lambda(
    ["subId", "usr", "score"],
    Do(
      If(
        IsNonEmpty(
          Paginate(
            Match(Index("evals_by_submission_and_user"), [
              Ref(Collection("Submission"), Var("subId")),
              Ref(Collection("User"), Var("usr"))
            ])
          )
        ),
        null,
        Create(Collection("Eval"), {
          data: {
            score: Var("score"),
            from: Ref(Collection("User"), Var("usr")),
            submission: Ref(Collection("Submission"), Var("subId"))
          }
        })
      ),
      Select(
        ["data", 0],
        Mean(
          Paginate(
            Match(
              Index("eval_scores_by_submission"),
              Ref(Collection("Submission"), Var("subId"))
            )
          )
        )
      )
    )
  )
)

Then I adapted the Union query you provided

Union(
  Match(Index('submission_by_status'),'VERIFIED'),
  Difference(Documents(Collection('Submission')),Match(Index('submission_by_user'),'current user'))
)

and ended up with something like this:

Paginate(
        Intersection(
          Match(Index("submissions_by_status"), "VERIFIED"),
          Difference(
            Documents(Collection("Submission")),
            Match(
              Index("user_evals_by_user_value_submission"),
              Ref(Collection("User"), Var("userId"))
            )
          )
        )
      )

which succeeds at returning submissions user did not evaluate :v:

Thanks again!