57

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

例如,如果我有

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

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

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

4

10 回答 10

49

您目前有 3 个选项:

选项1:客户端

这基本上就是你提到的方法。从集合中选择所有并在客户端进行计数。这对于小型数据集来说足够好,但如果数据集更大,显然不起作用。

选项 2:写时尽力而为

通过这种方法,您可以使用 Cloud Functions 为集合中的每次添加和删除更新计数器。

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

如果需要每秒超过 1 个,您需要按照我们的文档实现分布式计数器

选项 3:精确写入时间

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

与选项 2 一样,如果您想超过每秒,则需要实现分布式计数器

于 2017-10-03T22:04:35.110 回答
9

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

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

   db.collection("logs")
      .get()
      .then((res) => console.log(res.size));
于 2018-03-21T12:03:01.243 回答
4

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

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

这里,values是一个包含所有项目的数组myCollection。您不需要元数据,因此您可以valueChanges()直接使用方法。

于 2018-01-31T10:22:25.173 回答
2

小心计算具有云功能的大型集合的文档数量。如果您想为每个集合设置一个预先计算的计数器,那么使用 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 在所有情况下都相同。不幸的是,您的数据库中必须有“事件”集合。

于 2018-10-25T06:34:28.967 回答
1

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

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

于 2019-09-14T10:49:09.327 回答
1

firebase-admin允许select(fields)您仅获取集合中文档的特定字段。使用select比获取所有字段更高效。但是,它仅适用于firebase-admin并且firebase-admin通常仅用于服务器端。

select可以如下使用:

select('age', 'name') // fetch the age and name fields
select() // select no fields, which is perfect if you just want a count

select可用于 Node.js 服务器,但我不确定其他语言:

https://googleapis.dev/nodejs/firestore/latest/Query.html#select https://googleapis.dev/nodejs/firestore/latest/CollectionReference.html#select

这是一个用 Node.js 编写的服务器端云函数,用于select计算过滤后的集合并获取所有结果文档的 ID。它是用 TS 编写的,但很容易转换为 JS。

import admin from 'firebase-admin'

// https://stackoverflow.com/questions/46554091/cloud-firestore-collection-count

// we need to use admin SDK here as select() is only available for admin
export const videoIds = async (req: any): Promise<any> => {

  const id: string = req.query.id || null
  const group: string = req.query.group || null
  let processed: boolean = null
  if (req.query.processed === 'true') processed = true
  if (req.query.processed === 'false') processed = false

  let q: admin.firestore.Query<admin.firestore.DocumentData> = admin.firestore().collection('videos')
  if (group != null) q = q.where('group', '==', group)
  if (processed != null) q = q.where('flowPlayerProcessed', '==', processed)
  // select restricts returned fields such as ... select('id', 'name')
  const query: admin.firestore.QuerySnapshot<admin.firestore.DocumentData> = await q.orderBy('timeCreated').select().get()

  const ids: string[] = query.docs.map((doc: admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>) => doc.id) // ({ id: doc.id, ...doc.data() })

  return {
    id,
    group,
    processed,
    idx: id == null ? null : ids.indexOf(id),
    count: ids.length,
    ids
  }
}

对于 500 个文档的集合,云函数 HTTP 请求在 1 秒内完成,其中每个文档包含大量数据。性能并不惊人,但比不使用要好得多select。通过引入客户端缓存(甚至服务器端缓存)可以提高性能。

云函数入口点如下所示:

exports.videoIds = functions.https.onRequest(async (req, res) => {
  const response: any = await videoIds(req)
  res.json(response)
})

HTTP 请求 URL 将是:

https://SERVER/videoIds?group=my-group&processed=true

Firebase 函数详细说明了服务器在部署时的位置。

于 2022-01-12T15:09:53.417 回答
0

获取新的写入批次

WriteBatch batch = db.batch();

为“NYC”系列增添新价值

DocumentReference nycRef = db.collection("cities").document();
batch.set(nycRef, new City());

维护一个IDCount初始值为 total=0的文档

在添加操作期间执行如下

DocumentReference countRef= db.collection("cities").document("count");
batch.update(countRef, "total", FieldValue.increment(1));

在删除操作期间执行如下

DocumentReference countRef= db.collection("cities").document("count");
batch.update(countRef, "total", FieldValue.increment(-1));

总是从

DocumentReference nycRef = db.collection("cities").document("count");
于 2021-12-30T06:33:04.420 回答
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/post/Zebl6sSbaLdrnSFKbCJx/firestore-counters GitHub:https ://github.com/jdgamble555/adv-firestore-functions

于 2020-11-15T21:06:29.117 回答
0

按照 Dan 的回答:您可以在数据库中有一个单独的计数器并使用 Cloud Functions 来维护它。(写时尽力而为

// Example of performing an increment when item is added
module.exports.incrementIncomesCounter = collectionRef.onCreate(event => {
  const counterRef = event.data.ref.firestore.doc('counters/incomes')

  counterRef.get()
  .then(documentSnapshot => {
    const currentCount = documentSnapshot.exists ? documentSnapshot.data().count : 0

    counterRef.set({
      count: Number(currentCount) + 1
    })
    .then(() => {
      console.log('counter has increased!')
    })
  })
})

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

于 2018-01-25T21:53:56.447 回答
-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;
                }
            });
于 2020-06-08T19:51:09.457 回答