首页 > 解决方案 > Firebase Realtime DB:按键的值数量对查询结果进行排序

问题描述

我有一个带有用户的 Firebase Web Realtime DB,每个用户都有一个jobs属性,其值是一个对象:

{
  userid1:
    jobs:
      guid1: {},
      guid2: {},
  userid2:
    jobs:
      guid1: {},
      guid2: {},
}

我想查询以获得最多工作的 n 个用户。我可以使用 orderby 技巧按给定用户在其jobs属性中具有的值的数量对用户进行排序吗?

我特别不想存储每个用户拥有的作业数量的整数计数,因为我需要更新用户的jobs属性作为同时原子更新其他用户属性的原子更新的一部分,而且我不相信事务(像递增/递减计数器)可以成为这些原子事务的一部分。

这是我正在做的那种原子更新的例子。请注意,当我运行以下更新时,我在内存中没有我正在修改的用户:

firebase.database().ref('/').update({
  [`/users/${user.guid}/pizza`]: true,
  [`/users/${user.guid}/jobs/${job.guid}/scheduled`]: true,
})

非常感谢任何有关适用于这些数据的模式的建议!

标签: firebasefirebase-realtime-database

解决方案


实时数据库事务在 JSON 树中的单个节点上运行,因此很难将jobCounter原子更新中的节点更新集成到多个节点(即 to /users/${user.guid}/pizza/users/${user.guid}/jobs/${job.guid}/scheduled)。我们需要更新/users/${user.guid}级别并计算计数器值等...

一种更简单的方法是使用云函数来更新用户的jobCounter节点,每次有一个jobs节点发生变化,这意味着计数器的变化。换句话说,如果添加或删除了新的作业节点,则更新计数器。如果仅修改现有节点,则不会更新计数器,因为作业数量没有变化。

exports.updateJobsCounter = functions.database.ref('/users/{userId}/jobs')
    .onWrite((change, context) => {

        if (!change.after.exists()) {

            //This is the case when no more jobs exist for this user

            const userJobsCounterRef = change.before.ref.parent.child('jobsCounter');
            return userJobsCounterRef.transaction(() => {
                return 0;
            });

        } else {

            if (!change.before.val()) {

                //This is the case when the first job is created

                const userJobsCounterRef = change.before.ref.parent.child('jobsCounter');
                return userJobsCounterRef.transaction(() => {
                    return 1;
                });

            } else {

                const valObjBefore = change.before.val();
                const valObjAfter = change.after.val();

                const nbrJobsBefore = Object.keys(valObjBefore).length;
                const nbrJobsAfter = Object.keys(valObjAfter).length;

                if (nbrJobsBefore !== nbrJobsAfter) {
                    //We update the jobsCounter node
                    const userJobsCounterRef = change.after.ref.parent.child('jobsCounter');
                    return userJobsCounterRef.transaction(() => {
                        return nbrJobsAfter;
                    });
                } else {
                    //No need to update the jobsCounter node
                    return null;
                }

            }

        }

    });

推荐阅读