是否可以使用新的Firebase数据库Cloud Firestore来计算一个集合有多少项?
如果是,我该怎么做?
是否可以使用新的Firebase数据库Cloud Firestore来计算一个集合有多少项?
如果是,我该怎么做?
当前回答
没有直接的选择。不能执行db.collection("CollectionName").count()。 下面是查找集合中文档数量的两种方法。
1:-得到集合中的所有文件,然后得到它的大小。(不是最好的解决方案)
db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})
通过使用上述代码,您的文档读取的大小将等于集合中的文档大小,这就是为什么必须避免使用上述解决方案的原因。
2:-创建一个单独的文档与在您的集合,将存储在集合中的文件的数量计数。(最佳解决方案)
db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})
上面我们创建了一个带有名称计数的文档来存储所有计数信息。您可以通过以下方式更新计数文档:—
在文档计数上创建一个触发器 在创建新文档时,增加counts文档的count属性。 删除文档时,递减counts文档的count属性。
w.r.t价格(文档读取= 1)和快速数据检索上述解决方案是很好的。
其他回答
没有直接的选择。不能执行db.collection("CollectionName").count()。 下面是查找集合中文档数量的两种方法。
1:-得到集合中的所有文件,然后得到它的大小。(不是最好的解决方案)
db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})
通过使用上述代码,您的文档读取的大小将等于集合中的文档大小,这就是为什么必须避免使用上述解决方案的原因。
2:-创建一个单独的文档与在您的集合,将存储在集合中的文件的数量计数。(最佳解决方案)
db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})
上面我们创建了一个带有名称计数的文档来存储所有计数信息。您可以通过以下方式更新计数文档:—
在文档计数上创建一个触发器 在创建新文档时,增加counts文档的count属性。 删除文档时,递减counts文档的count属性。
w.r.t价格(文档读取= 1)和快速数据检索上述解决方案是很好的。
和许多问题一样,答案是——视情况而定。
在前端处理大量数据时应该非常小心。除了让你的前端感觉迟钝之外,Firestore还会向你收取每百万次读取60美元的费用。
小型收藏(少于100份文件)
小心使用-前端用户体验可能会受到影响
在前端处理这个应该没问题,只要你没有对这个返回的数组做太多的逻辑处理。
db.collection('...').get().then(snap => {
size = snap.size // will return the collection size
});
中等藏书(100至1000份)
小心使用- Firestore读取调用可能会花费很多
在前端处理这个问题是不可行的,因为它有很大的可能会降低用户系统的速度。我们应该处理这个逻辑服务器端,只返回大小。
这种方法的缺点是您仍然在调用Firestore读取(等于您的集合的大小),从长远来看,这最终可能会使您的成本超过预期。
云功能:
db.collection('...').get().then(snap => {
res.status(200).send({length: snap.size});
});
前端:
yourHttpClient.post(yourCloudFunctionUrl).toPromise().then(snap => {
size = snap.length // will return the collection size
})
大量的收集(1000+文档)
最具可扩展性的解决方案
FieldValue.increment ()
截至2019年4月,Firestore现在允许增量计数器,完全原子,无需事先读取数据。这确保了即使同时从多个源进行更新(以前使用事务解决)也能获得正确的计数器值,同时还减少了执行的数据库读取次数。
通过监听任何删除或创建的文档,我们可以向数据库中的计数字段添加或删除。
参见firestore文档-分布式计数器 或者看看杰夫·德莱尼的《数据聚合》。他的指南对于任何使用AngularFire的人来说都是非常棒的,但他的课程也应该适用于其他框架。
云功能:
export const documentWriteListener = functions.firestore
.document('collection/{documentUid}')
.onWrite((change, context) => {
if (!change.before.exists) {
// New document Created : add one to count
db.doc(docRef).update({ numberOfDocs: FieldValue.increment(1) });
} else if (change.before.exists && change.after.exists) {
// Updating existing document : Do nothing
} else if (!change.after.exists) {
// Deleting document : subtract one from count
db.doc(docRef).update({ numberOfDocs: FieldValue.increment(-1) });
}
return;
});
现在在前端,你可以查询这个numberOfDocs字段来获得集合的大小。
FireStore现在支持该功能,尽管是Beta版。 下面是Firebase的官方文档
聚合计数查询刚刚在Firestore中预览。
在2022年Firebase峰会上宣布:https://firebase.blog/posts/2022/10/whats-new-at-Firebase-Sumit-2022
摘录:
[开发人员预览]Count()函数:与新的计数函数 Firstore[原文],你现在可以得到匹配文件的计数当你 运行查询或从集合中读取,而不加载实际的 文件,这为你节省了很多时间。
他们在峰会上展示的代码示例:
在问答环节中,有人问了汇总查询的定价问题,Firebase团队给出的答案是,它的成本是读取价格的1 / 1000(四舍四入到最近的读取,详情见下面的评论),但将计算汇总的所有记录。
对于大量的收藏,要仔细计算文件的数量。如果你想为每个收集都有一个预先计算好的计数器,那么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在所有情况下都是相同的。不幸的是,您的数据库中必须有“事件”集合。