如何使用Cloud Firestore计算集合中的文档数量

时间:2017-10-03 20:58:00

标签: database firebase google-cloud-firestore

在Firestore中,如何获取集合中的文档总数?

例如,如果我有

/people
    /123456
        /name - 'John'
    /456789
        /name - 'Jane'

我想查询我有多少人并获得2。

我可以对/ people进行查询然后获取返回结果的长度,但这似乎是浪费,特别是因为我将在更大的数据集上执行此操作。

8 个答案:

答案 0 :(得分:32)

您目前有3个选项:

选项1:客户端

这基本上就是你提到的方法。从集合中选择全部并在客户端计数。这适用于小型数据集,但如果数据集较大,则显然不起作用。

选项2:写入时间尽力而为

使用此方法,您可以使用云功能更新计数器,以便从集合中添加和删除。

这适用于任何数据集大小,只要添加/删除仅以小于或等于每秒1的速率发生。这为您提供了一个单独的文档,可以立即为您提供几乎当前的计数。

如果需要每秒超过1次,则需要实施distributed counters per our documentation

选项3:准确写入时间

在客户端中,您可以在添加或删除文档的同时更新计数器,而不是使用云功能。这意味着计数器也是最新的,但您需要确保在添加或删除文档的任何位置包含此逻辑。

与选项2一样,如果要超过每秒

,则需要实现分布式计数器

答案 1 :(得分:3)

如果你使用AngulareFire2,你可以这样做(假设你的构造函数中注入了private afs: AngularFirestore):

this.afs.collection(myCollection).valueChanges().subscribe( values => console.log(values.length));

此处,valuesmyCollection中所有项目的数组。您不需要元数据,因此您可以直接使用valueChanges()方法。

答案 2 :(得分:2)

聚合是可行的方法(firebase函数看起来像更新这些聚合的推荐方式,因为客户端向您不希望公开的用户公开信息)https://firebase.google.com/docs/firestore/solutions/aggregation

另一种方式(不推荐)对大型列表不利并涉及下载整个列表:res.size就像这个例子:

db.collection('products').get().then(res => console.log(res.size))

答案 3 :(得分:1)

请小心计算具有云功能的大型馆藏的文档数量。如果您希望每个集合都有一个预先计算的计数器,那么对于Firestore数据库来说有点复杂。

在这种情况下,此类代码无效:

export const customerCounterListener = 
    functions.firestore.document('customers/{customerId}')
    .onWrite((change, context) => {

    // on create
    if (!change.before.exists && change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count + 1
                     }))
    // on delete
    } else if (change.before.exists && !change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count - 1
                     }))
    }

    return null;
});

原因是因为每个云Firestore触发器都必须是幂等的,如Firestore文档所述:https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees

解决方案

因此,为了防止多次执行代码,您需要使用事件和事务进行管理。这是我处理大型收款柜台的特殊方式:

const executeOnce = (change, context, task) => {
    const eventRef = firestore.collection('events').doc(context.eventId);

    return firestore.runTransaction(t =>
        t
         .get(eventRef)
         .then(docSnap => (docSnap.exists ? null : task(t)))
         .then(() => t.set(eventRef, { processed: true }))
    );
};

const documentCounter = collectionName => (change, context) =>
    executeOnce(change, context, t => {
        // on create
        if (!change.before.exists && change.after.exists) {
            return t
                    .get(firestore.collection('metadatas')
                    .doc(collectionName))
                    .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: ((docSnap.data() && docSnap.data().count) || 0) + 1
                        }));
        // on delete
        } else if (change.before.exists && !change.after.exists) {
            return t
                     .get(firestore.collection('metadatas')
                     .doc(collectionName))
                     .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: docSnap.data().count - 1
                        }));
        }

        return null;
    });

这里的用例:

/**
 * Count documents in articles collection.
 */
exports.articlesCounter = functions.firestore
    .document('articles/{id}')
    .onWrite(documentCounter('articles'));

/**
 * Count documents in customers collection.
 */
exports.customersCounter = functions.firestore
    .document('customers/{id}')
    .onWrite(documentCounter('customers'));

如您所见,防止重复执行的关键是上下文对象中名为 eventId 的属性。如果针对同一事件多次处理该函数,则事件ID在所有情况下均相同。不幸的是,您必须在数据库中具有“事件”集合。

答案 4 :(得分:0)

关注Dan回答:您可以在数据库中使用单独的计数器并使用云功能来维护它。 (写作时尽力而为

2018-01-25 21:28:13.597 Info: {"firstName":"Hugo", "lastName":"Koopmans", "userName":"xxxxxx", "email":"xxx@xxx.com", "password":"xxxxx", "activationCode":"4db600e771f85073a03ed16371ba39ad3b408852", "active":false, "id":"xxxxxx", "devices":["xxxxxxx"]}
2018-01-25 21:29:47.559 Notice: SVC-CANCELED: xdmp.documentInsert("/users/952187962xx.json", ObjectNode({"firstName":"Hugo", "lastName":"Koopmans", "userName":"xxxxx", "email":"...", ...}), Sequence({...XDMP-CANCELED: Canceled request "/v1/users/passwordreset"..., {...XDMP-CANCELED: Canceled request "/v1/users/passwordreset"...)) -- Canceled
2018-01-25 21:29:47.559 Notice:+in xdmp:eval("declareUpdate(); xdmp.documentInsert(uri, doc,[xdmp.permission(u...", {uri:"/users/952187962xxxxx.json", userName:"xxxxx", doc:{firstName:"Hugo", lastName:"Koopmans", userName:"xxxxx", ...}}, {isolation:"different-transaction", userId:"9521879xxxx"}) [javascript]
2018-01-25 21:29:47.559 Notice:+in /v1/users.sjs [javascript]
2018-01-25 21:29:47.559 Notice:+in /v1/users.sjs [javascript]
2018-01-25 21:29:48.535 Notice: XDMP-CANCELED: Canceled request "/MarkLogic/rest-api/error-handler.xqy"
2018-01-25 21:29:48.535 Notice:+in /MarkLogic/manage/endpoints/databases/partitions/partitions.xqy, at 184:9, in pa:get-partition() [1.0-ml]
2018-01-25 21:29:48.535 Notice: XDMP-CANCELED: Canceled request "/MarkLogic/rest-api/error-handler.xqy"
2018-01-25 21:29:48.535 Notice:+in /MarkLogic/rest-api/error-handler.xqy [1.0-ml]

此代码显示了如何执行此操作的完整示例: https://gist.github.com/saintplay/3f965e0aea933a1129cc2c9a823e74d7

答案 5 :(得分:0)

请检查以下我在另一个线程上找到的答案。您的数量应该是原子的。在这种情况下,必须使用 FieldValue.increment()函数。

https://stackoverflow.com/a/49407570/3337028

答案 6 :(得分:0)

我创建了一个NPM包来处理所有计数器:

首先将模块安装在您的功能目录中:

npm i adv-firestore-functions

然后像这样使用它:

import { eventExists, colCounter } from 'adv-firestore-functions';

functions.firestore
    .document('posts/{docId}')
    .onWrite(async (change: any, context: any) => {

    // don't run if repeated function
    if (await eventExists(context)) {
      return null;
    }

    await colCounter(change, context);
}

它处理事件以及其他所有事情。

如果要使其成为所有功能的通用计数器:

import { eventExists, colCounter } from 'adv-firestore-functions';

functions.firestore
    .document('{colId}/{docId}')
    .onWrite(async (change: any, context: any) => {

    const colId = context.params.colId;

    // don't run if repeated function
    if (await eventExists(context) || colId.startsWith('_')) {
      return null;
    }

    await colCounter(change, context);
}

不要忘记您的规则:

match /_counters/{document} {
  allow read;
  allow write: if false;
}

当然可以这样访问它:

const collectionPath = 'path/to/collection';
const colSnap = await db.doc('_counters/' + collectionPath).get();
const count = colSnap.get('count');

了解更多:https://fireblog.io/blog/post/firestore-counters
GitHub:https://github.com/jdgamble555/adv-firestore-functions

答案 7 :(得分:-1)

使用Transaction更新数据库写入成功侦听器中的计数。

FirebaseFirestore.getInstance().runTransaction(new Transaction.Function<Long>() {
                @Nullable
                @Override
                public Long apply(@NonNull Transaction transaction) throws FirebaseFirestoreException {
                    DocumentSnapshot snapshot = transaction
                            .get(pRefs.postRef(forumHelper.getPost_id()));
                    long newCount;
                    if (b) {
                        newCount = snapshot.getLong(kMap.like_count) + 1;
                    } else {
                        newCount = snapshot.getLong(kMap.like_count) - 1;
                    }

                    transaction.update(pRefs.postRef(forumHelper.getPost_id()),
                            kMap.like_count, newCount);

                    return newCount;
                }
            });