brandbei37
brandbei37

Reputation: 501

Firebase realtime database order by child with most children

I have a database which allows users to 'like' comments given on posts.

When I originally fetch the comments, is there a way to order comments by 'highest' liked first?

Now I know I could do .orderByChild() but the problem is I don't have a 'like' value child.

The way I process 'likes' is to save the users UID and timestamp inside the original commentID child.

Is it possible to fetch comments based on the child with the most children inside?

My database looks like:

{
 "users" : {
  "$uid" : {
   "posts" : {
    "$postID" : {
     "postText" : "blah blah blah",
      "comments" : {
       "$commentID" : {
        "commentText" : "blah blah blah",
         "likes" : {
          "UID01" : "1622035190516",
          "UID02" : "1622036141955",
          "UID03" : "1622036145134",
         }
       },
       "$commentID" : {
        "commentText" : "blah 2 blah 2 blah 2",
         "likes" : {
          "UID01" : "1622036141955",
          "UID02" : "1622036145134",
         }
       }
      }
    }
   }
  }
 }
}

Upvotes: 0

Views: 323

Answers (1)

Renaud Tarnec
Renaud Tarnec

Reputation: 83093

Is it possible to fetch comments based on the child with the most children inside?

No you cannot sort data based on an aggregation calculated on the fly.

One classical solution for this case in the NoSQL world is to maintain a counter. One of the best approaches for that is to use a Cloud Function: the calculation is done in the backend via the Admin SDK and therefore you can protect the database nodes containing the number of likes with a security rule in such a way the users cannot modify their values.


More concretely, you should:

1/ Initialize the counter to 1 when you write the first like. You would use an onCreate Cloud Function along the following lines:

exports.initializeCounter = functions.database.ref('users/{uid}/posts/{postID}/comments/{commentID}/likes')
    .onCreate((snapshot, context) => {
      return snapshot.ref.parent.child('nbrOfLikes').set(1);
    });

2/ Update the counter each time you add or remove a like with an onUpdate Cloud Function in which you count the nbr of likes and update the counter accordingly. The following Cloud Function will do the job.

exports.updateCounter = functions.database.ref('users/{uid}/posts/{postID}/comments/{commentID}/likes')
    .onUpdate((change, context) => {
        const likeObject = change.after.val();
        return change.after.ref.parent.child('nbrOfLikes').set(Object.keys(likeObject).length);
    });

You could alternatively use the numChildren() method, see this answer.


Note that you would need to treat the case where all the likes are deleted, if this case happens in your app.


Note also that the two (or three) Cloud Functions could be merged in one onWrite Cloud Function.

Upvotes: 1

Related Questions