是否可以使用新的Firebase数据库Cloud Firestore来计算一个集合有多少项?

如果是,我该怎么做?


当前回答

有了新版本的Firebase,您现在可以运行聚合查询了! 简单的写

.count().get(); 

在您的询问之后。

其他回答

对于大量的收藏,要仔细计算文件的数量。如果你想为每个收集都有一个预先计算好的计数器,那么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;
});

原因是每个云防火墙触发器都必须是幂等的,正如防火墙文档所示: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在所有情况下都是相同的。不幸的是,您的数据库中必须有“事件”集合。

Firestore引入了一个新的query .count(),它获取查询的计数而不获取文档。

这将允许简单地查询所有集合项并获得该查询的计数。

Ref:

Firebase 10 iOS SDK [JS SDK PR] (https://github.com/firebase/firebase-js-sdk/pull/6608)

聚合计数查询刚刚在Firestore中预览。

在2022年Firebase峰会上宣布:https://firebase.blog/posts/2022/10/whats-new-at-Firebase-Sumit-2022

摘录:

[开发人员预览]Count()函数:与新的计数函数 Firstore[原文],你现在可以得到匹配文件的计数当你 运行查询或从集合中读取,而不加载实际的 文件,这为你节省了很多时间。

他们在峰会上展示的代码示例:

在问答环节中,有人问了汇总查询的定价问题,Firebase团队给出的答案是,它的成本是读取价格的1 / 1000(四舍四入到最近的读取,详情见下面的评论),但将计算汇总的所有记录。

根据本文档,Cloud Firestore支持count()聚合查询,并在预览版中可用。

颤振/飞镖代码丢失(在写这篇文章的时候),所以我玩了一下,下面的函数似乎可以工作:

  Future<int> getCount(String path) async {
    var collection = _fireStore.collection(path);
    var countQuery = collection.count();
    var snapShot = await countQuery.get(source: AggregateSource.server);
    return snapShot.count;
  }

截至2022年10月,Firestore在客户端sdk上引入了count()方法。现在您可以在没有下载的情况下计算查询。

对于1000份文件,它将收取你阅读1份文件的费用。

网络(v9)

Firebase 9.11.0介绍:

const collectionRef = collection(db, "cities");
const snapshot = await getCountFromServer(collectionRef);
console.log('count: ', snapshot.data().count);

Web V8

不可用。

节点(管理)

const collectionRef = db.collection('cities');
const snapshot = await collectionRef.count().get();
console.log(snapshot.data().count);

Android (Kotlin)

在firestore v24.4.0 (BoM 31.0.0)引入:

val query = db.collection("cities")
val countQuery = query.count()
countQuery.get(AggregateSource.SERVER).addOnCompleteListener { task ->
    if (task.isSuccessful) {
        val snapshot = task.result
        Log.d(TAG, "Count: ${snapshot.count}")
    } else {
        Log.d(TAG, "Count failed: ", task.getException())
    }
}

苹果平台(Swift)

Firestore v10.0.0引入:

do {
  let query = db.collection("cities")
  let countQuery = query.countAggregateQuery
  let snapshot = try await countQuery.aggregation(source: AggregateSource.server)
  print(snapshot.count)
} catch {
  print(error)
}