How to join results on a firebase query the right way

firebase limit query
firebase get user data
firebase rest api
retrieve data from firebase c#
firebase query contains
firebase query android
firebase where query

I am using Firebase real-time database for my app build with Unity. In order to build a "friend" leaderboard, the database will keep track of users their friends and their scores.

The database has the following structure:

scores{
user id : score
}
Users:{
    Id: {
        ageRange
        email
        name
        friendlist : {
             multiple friends user ids
        }
    }
}

The problem is in order to get the scores and the names of every friend the app has to make alot of api calls. Atleast if I correctly understand firebase. If the user has 10 friends it will take 21 calls before the leaderboard is filled.

I came up with the following code written in c#:

List<UserScore> leaderBoard = new List<UserScore>();
db.Child("users").Child(uid).Child("friendList").GetValueAsync().ContinueWith(task => {
    if (task.IsCompleted)
    {
        //foreach friend
        foreach(DataSnapshot h in task.Result.Children)
        {
            string curName ="";
            int curScore = 0;
            //get his name in the user table
            db.Child("users").Child(h.Key).GetValueAsync().ContinueWith(t => {
                if (t.IsCompleted)
                {
                    DataSnapshot s = t.Result;
                    curName = s.Child("name").Value.ToString();
                    //get his score from the scores table
                    db.Child("scores").Child(h.Key).GetValueAsync().ContinueWith(q => {
                        if (q.IsCompleted)
                        {
                            DataSnapshot b = q.Result;
                            curScore = int.Parse(b.Value.ToString());
                            //make new userscore and add to leaderboard
                            leaderBoard.Add(new UserScore(curName, curScore));
                            Debug.Log(curName);
                            Debug.Log(curScore.ToString());
                        }
                    });
                }
            });
        }
    }
});

Is there any other way to do this? I've read multiple stack overflow questions watched firebase tutorials but i didnt found any simpler or more efficient way to get the job done.

There's not a way of reducing the number of API calls without duplicating data/restructuring your database. However, reducing API calls doesn't necessarily mean the overall read strategy is faster/better. My suggestion would be to optimize your reading strategy to reduce overall data read and to make reads concurrently when possible.

Solution 1: Optimize reading strategy

This is my recommended solution, because it doesn't include extra unnecessary data nor does it include managing consistency of your data.

Your code should look something like below: (DISCLAIMER: I'm not a C# programmer, so there might be some errors)

List<UserScore> leaderBoard = new List<UserScore>();
db.Child("users").Child(uid).Child("friendList").GetValueAsync().ContinueWith(task => {
    if (task.IsCompleted)
    {
        //foreach friend
        foreach(DataSnapshot h in task.Result.Children)
        {
            // kick off the task to retrieve friend's name
            Task nameTask = db.Child("users").Child(h.Key).Child("name").GetValueAsync();
            // kick off the task to retrieve friend's score
            Task scoreTask = db.Child("scores").Child(h.Key).GetValueAsync();
            // join tasks into one final task
            Task finalTask = Task.Factory.ContinueWhenAll((new[] {nameTask, scoreTask}), tasks => {
              if (nameTask.IsCompleted && scoreTask.IsCompleted) {
                // both tasks are complete; add new record to leaderboard
                string name = nameTask.Result.Value.ToString();
                int score = int.Parse(scoreTask.Result.Value.ToString());
                leaderBoard.Add(new UserScore(name, score));
                Debug.Log(name);
                Debug.Log(score.ToString());
              }
            })
        }
    }
});

The above code improves the overall read strategy by not pulling all of a friend's user data (i.e. name, email, friendlist, etc.) and by pulling the name concurrently with score.

Solution 2: Duplicate name to scores table

If this still isn't optimal enough, you can always duplicate the friend's name in their score table. Something like below:

scores: {
  <user_id>: {
    name: <user_name>,
    score: <user_score>
  }
}

This would then allow you to only make one call per friend instead of two. However, you will still be reading the same amount of data, and you will have to manage the consistency of the data (either use a Firebase Function to propagate user name changes or write to both places).

Solution 3: Combine scores table into users table

If you don't want to manage the consistency issue, you can just combine the scores table into the users table. Your structure would be something like:

users: {
  <user_id>: {
    name: <user_name>,
    ...,
    score: <user_score>
  }
}

However, in this instance, you will be reading more data (email, friendlist, etc.)

I hope this helps.

c# - How to join results on a firebase query the right way, There's not a way of reducing the number of API calls without duplicating data/​restructuring your database. However, reducing API calls doesn't necessarily  Firebase - Queries - Firebase offers various ways of ordering data. In this chapter, we will show simple query examples. We will use the same data from our previous chapters.

While the following will not reduce the number of calls, it will create tasks for the retrieval of the data and run them all simultaneously, returning the list of desired user scores.

var friendList = await db.Child("users").Child(uid).Child("friendList").GetValueAsync();

List<Task<UserScore>> tasks = new List<Task<UserScore>>();
//foreach friend
foreach(DataSnapshot friend in friendList.Children) {
    var task = Task.Run( async () => {
        var friendKey = friend.Key;
        //get his name in the user table
        var getName = db.Child("users").Child(friendKey).Child("name").GetValueAsync();
        //get his score from the scores table
        var getScore = db.Child("scores").Child(friendKey).GetValueAsync();

        await Task.WhenAll(getName, getScore);

        var name = getName.Result.Value.ToString();
        var score = int.Parse(getScore.Result.Value.ToString());

        //make new userscore to add to leader board
        var userScore = new UserScore(name, score);
        Debug.Log($"{name} : {score}");
        return userScore;
    });
    tasks.Add(task);
}

var scores = await Task.WhenAll(tasks);

List<UserScore> leaderBoard = new List<UserScore>(scores);

Joins in the Firebase Database, The REST API accepts several query parameters when reading data from our Firebase Then, you combine orderBy with any of the other five parameters: limitToFirst If the order of your data is important you should sort the results in your To query the height now, we use the full path to the object rather than a single key:. See Get Data for more information on retrieving query results. You can also add a listener to a query to get the current results and listen for future updates. Query operators. The where() method takes three parameters: a field to filter on, a comparison operation, and a value.

This is mostly database structure issue.

First, you need leaderboard table.

leaderboard: {
    <user_id>: <score>
}

Second, you need users table.

users: {
    <user_id>: {
        name: <user_name>,
        ...,
        score: <user_score>,
        friendlist: {
            multiple friends user ids
        }
    }
}

And you have to update leaderboard's score and users' score at the same time.

If you want to avoid Callback hell.

You can also try like this. (This code is JAVA)

// Create a new ThreadPoolExecutor with 2 threads for each processor on the
// device and a 60 second keep-alive time.
int numCores = Runtime.getRuntime().availableProcessors();
ExecutorService executor = new ThreadPoolExecutor(
        numCores * 2,
        numCores * 2,
        60L,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<Runnable>()
);

Tasks.call(executor, (Callable<Void>) () -> {
    Task<Token> getStableTokenTask = NotificationUtil.getStableToken(token);
    Token stableToken;
    stableToken = Tasks.await(getStableTokenTask);

    if (stableToken != null) {
        Task<Void> updateStableTokenTask = NotificationUtil.updateStableToken(stableToken.getId(), versionCode, versionName);
        Tasks.await(updateStableTokenTask);
    }

    if (stableToken == null) {
        Token newToken = new Token(token, versionCode, versionName);
        Task<Void> insertStableTokenTask = NotificationUtil.insertStableToken(newToken);
        Tasks.await(insertStableTokenTask);
    }

    return null;
}).continueWith((Continuation<Void, Void>) task -> {
    if (!task.isSuccessful()) {
        // You will catch every exceptions from here.
        Log.w(TAG, task.getException());
        return null;
    }
    return null;
});

Retrieving Data, Optimize Monetization Strategies · Prevent Churn · Explore Prediction Results · Export Advanced queries in Cloud Firestore allow you to quickly find documents in One way to achieve consistency is to perform the add and the update in a by writing advanced security rules, this may not be appropriate in all situations. But again if you don’t really need the related data in real time you can use this easy way to join two references. For a complete reference of Firebase use this link. For a complete list of querying functions you ca use this link. If you found this post useful please don’t forget to press the like button and share it.

Retrieving Data, Understand storage size calculations · Best practices for Cloud Firestore Use the push() method to append data to a list in multiuser applications. You can use the Realtime Database Query class to retrieve data sorted by key, For example, you can combine the startAt() and endAt() methods to limit the results to a  Firebase collections are meant to be consumed as streams of data, so imagine running a query like fruitRef.orderByKey().limitToLast(3).on(‘child_added’, callback). Your callback will get called three times, once for each of the last three results.

Aggregation queries, Reference for Query. you can receive data from a Query by using the on() method. This can be used to filter result sets with many matches for the same value. This is the primary way to read data from a Database. Coming from a SQL background as I did, it can take a while to grok the freedom of NoSQL data structures and the simplicity of Firebase's dynamic, real-time query environment. Part 1 of this double-header will will cover some of the common queries we know and love and talk about how they can be converted to Firebase queries.

Work with Lists of Data on the Web, DZone > Mobile Zone > Firebase Advance Querying Join Reference data in real time you can use this easy way to join two references. SASS has a preprocessing that converts a .scss file to the required .css output. Android offers several utilities to help you format correctly across locales, such as:. Just as with a Reference, you can receive data from a Query by using the on() method. You will only receive events and DataSnapshot s for the subset of the data that matches your query.

Comments
  • Have you tried to get a list of the friends instead of getting them one by one?, just get users with a filter by ids. That will create 1 api call instead of N calls.
  • @DiegoCardozo in the first call I get back a list of friends (their ids). After that, I still need to get their username and score one by one thats why i created a foreach loop. Is there a way to like get all usernames of those friends in one call instead of looping and trying to do that for each friend?
  • What I mean was that you already have the list of ids, is your friendsList property. You could create a query to get a list of friends that match with those Ids.