【发布时间】:2021-12-03 01:10:33
【问题描述】:
是否可以使用新的 Firebase 数据库 Cloud Firestore 计算一个集合有多少项目?
如果是这样,我该怎么做?
【问题讨论】:
-
我想你可能也对这篇文章感兴趣,How to count the number of documents in a Firestore collection?。
标签: firebase google-cloud-firestore
是否可以使用新的 Firebase 数据库 Cloud Firestore 计算一个集合有多少项目?
如果是这样,我该怎么做?
【问题讨论】:
标签: firebase google-cloud-firestore
11/20 更新
我创建了一个 npm 包以便轻松访问计数器功能:https://fireblog.io/post/Zebl6sSbaLdrnSFKbCJx/firestore-counters
我使用所有这些想法创建了一个通用函数来处理所有计数器情况(查询除外)。
唯一的例外是当一秒钟写这么多的时候,它 减慢你的速度。一个例子是热门帖子上的likes。它是 例如,在博客文章中过度使用,并且会花费更多。一世 建议在这种情况下使用分片创建一个单独的函数: https://firebase.google.com/docs/firestore/solutions/counters
// trigger collections
exports.myFunction = functions.firestore
.document('{colId}/{docId}')
.onWrite(async (change: any, context: any) => {
return runCounter(change, context);
});
// trigger sub-collections
exports.mySubFunction = functions.firestore
.document('{colId}/{docId}/{subColId}/{subDocId}')
.onWrite(async (change: any, context: any) => {
return runCounter(change, context);
});
// add change the count
const runCounter = async function (change: any, context: any) {
const col = context.params.colId;
const eventsDoc = '_events';
const countersDoc = '_counters';
// ignore helper collections
if (col.startsWith('_')) {
return null;
}
// simplify event types
const createDoc = change.after.exists && !change.before.exists;
const updateDoc = change.before.exists && change.after.exists;
if (updateDoc) {
return null;
}
// check for sub collection
const isSubCol = context.params.subDocId;
const parentDoc = `${countersDoc}/${context.params.colId}`;
const countDoc = isSubCol
? `${parentDoc}/${context.params.docId}/${context.params.subColId}`
: `${parentDoc}`;
// collection references
const countRef = db.doc(countDoc);
const countSnap = await countRef.get();
// increment size if doc exists
if (countSnap.exists) {
// createDoc or deleteDoc
const n = createDoc ? 1 : -1;
const i = admin.firestore.FieldValue.increment(n);
// create event for accurate increment
const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);
return db.runTransaction(async (t: any): Promise<any> => {
const eventSnap = await t.get(eventRef);
// do nothing if event exists
if (eventSnap.exists) {
return null;
}
// add event and update size
await t.update(countRef, { count: i });
return t.set(eventRef, {
completed: admin.firestore.FieldValue.serverTimestamp()
});
}).catch((e: any) => {
console.log(e);
});
// otherwise count all docs in the collection and add size
} else {
const colRef = db.collection(change.after.ref.parent.path);
return db.runTransaction(async (t: any): Promise<any> => {
// update size
const colSnap = await t.get(colRef);
return t.set(countRef, { count: colSnap.size });
}).catch((e: any) => {
console.log(e);
});;
}
}
这处理事件、增量和事务。这样做的好处是,如果您不确定文档的准确性(可能仍处于测试阶段),您可以删除计数器,让它在下一次触发时自动添加它们。是的,这是成本,所以不要删除它。
获取计数的方法相同:
const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const colSnap = await db.doc('_counters/' + collectionPath).get();
const count = colSnap.get('count');
此外,您可能希望创建一个 cron 作业(计划函数)来删除旧事件以节省数据库存储费用。您至少需要一个 blaze 计划,并且可能需要更多配置。例如,您可以在每周日晚上 11 点运行它。 https://firebase.google.com/docs/functions/schedule-functions
这是未经测试,但应该进行一些调整:
exports.scheduledFunctionCrontab = functions.pubsub.schedule('5 11 * * *')
.timeZone('America/New_York')
.onRun(async (context) => {
// get yesterday
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
const eventFilterSnap = await eventFilter.get();
eventFilterSnap.forEach(async (doc: any) => {
await doc.ref.delete();
});
return null;
});
最后,别忘了保护 firestore.rules 中的集合:
match /_counters/{document} {
allow read;
allow write: if false;
}
match /_events/{document} {
allow read, write: if false;
}
更新:查询
如果您还想自动化查询计数,添加到我的其他答案中,您可以在您的云函数中使用此修改后的代码:
if (col === 'posts') {
// counter reference - user doc ref
const userRef = after ? after.userDoc : before.userDoc;
// query reference
const postsQuery = db.collection('posts').where('userDoc', "==", userRef);
// add the count - postsCount on userDoc
await addCount(change, context, postsQuery, userRef, 'postsCount');
}
return delEvents();
这将自动更新 userDocument 中的 postsCount。您可以通过这种方式轻松地将其他计数添加到多个计数中。这只是让您了解如何使事情自动化。我还为您提供了另一种删除事件的方法。您必须阅读每个日期才能删除它,因此以后删除它们并不能真正节省您,只会使功能变慢。
/**
* Adds a counter to a doc
* @param change - change ref
* @param context - context ref
* @param queryRef - the query ref to count
* @param countRef - the counter document ref
* @param countName - the name of the counter on the counter document
*/
const addCount = async function (change: any, context: any,
queryRef: any, countRef: any, countName: string) {
// events collection
const eventsDoc = '_events';
// simplify event type
const createDoc = change.after.exists && !change.before.exists;
// doc references
const countSnap = await countRef.get();
// increment size if field exists
if (countSnap.get(countName)) {
// createDoc or deleteDoc
const n = createDoc ? 1 : -1;
const i = admin.firestore.FieldValue.increment(n);
// create event for accurate increment
const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);
return db.runTransaction(async (t: any): Promise<any> => {
const eventSnap = await t.get(eventRef);
// do nothing if event exists
if (eventSnap.exists) {
return null;
}
// add event and update size
await t.set(countRef, { [countName]: i }, { merge: true });
return t.set(eventRef, {
completed: admin.firestore.FieldValue.serverTimestamp()
});
}).catch((e: any) => {
console.log(e);
});
// otherwise count all docs in the collection and add size
} else {
return db.runTransaction(async (t: any): Promise<any> => {
// update size
const colSnap = await t.get(queryRef);
return t.set(countRef, { [countName]: colSnap.size }, { merge: true });
}).catch((e: any) => {
console.log(e);
});;
}
}
/**
* Deletes events over a day old
*/
const delEvents = async function () {
// get yesterday
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
const eventFilterSnap = await eventFilter.get();
eventFilterSnap.forEach(async (doc: any) => {
await doc.ref.delete();
});
return null;
}
我还应该警告您,通用功能将在每个 onWrite 调用周期。仅在其上运行该功能可能更便宜 onCreate 和 onDelete 特定集合的实例。喜欢 我们正在使用的noSQL数据库,重复的代码和数据可以为您节省 钱。
【讨论】:
快速+省钱的诀窍之一是:-
创建一个doc 并在firestore 中存储一个“计数”变量,当用户在集合中添加新文档时,增加该变量,当用户删除文档时,减少变量。例如
updateDoc(doc(db, "Count_collection", "Count_Doc"), {count: increment(1)});
注意:使用 (-1) 表示减少,(1) 表示增加count
如何节省金钱和时间:-
【讨论】:
除了上面我的 npm 包adv-firestore-functions,你也可以只使用firestore规则来强制一个好的计数器:
function counter() {
let docPath = /databases/$(database)/documents/_counters/$(request.path[3]);
let afterCount = getAfter(docPath).data.count;
let beforeCount = get(docPath).data.count;
let addCount = afterCount == beforeCount + 1;
let subCount = afterCount == beforeCount - 1;
let newId = getAfter(docPath).data.docId == request.path[4];
let deleteDoc = request.method == 'delete';
let createDoc = request.method == 'create';
return (newId && subCount && deleteDoc) || (newId && addCount && createDoc);
}
function counterDoc() {
let doc = request.path[4];
let docId = request.resource.data.docId;
let afterCount = request.resource.data.count;
let beforeCount = resource.data.count;
let docPath = /databases/$(database)/documents/$(doc)/$(docId);
let createIdDoc = existsAfter(docPath) && !exists(docPath);
let deleteIdDoc = !existsAfter(docPath) && exists(docPath);
let addCount = afterCount == beforeCount + 1;
let subCount = afterCount == beforeCount - 1;
return (createIdDoc && addCount) || (deleteIdDoc && subCount);
}
并像这样使用它们:
match /posts/{document} {
allow read;
allow update;
allow create: if counter();
allow delete: if counter();
}
match /_counters/{document} {
allow read;
allow write: if counterDoc();
}
用这些替换你的 set 和 delete 函数:
设置
async setDocWithCounter(
ref: DocumentReference<DocumentData>,
data: {
[x: string]: any;
},
options: SetOptions): Promise<void> {
// counter collection
const counterCol = '_counters';
const col = ref.path.split('/').slice(0, -1).join('/');
const countRef = doc(this.afs, counterCol, col);
const countSnap = await getDoc(countRef);
const refSnap = await getDoc(ref);
// don't increase count if edit
if (refSnap.exists()) {
await setDoc(ref, data, options);
// increase count
} else {
const batch = writeBatch(this.afs);
batch.set(ref, data, options);
// if count exists
if (countSnap.exists()) {
batch.update(countRef, {
count: increment(1),
docId: ref.id
});
// create count
} else {
// will only run once, should not use
// for mature apps
const colRef = collection(this.afs, col);
const colSnap = await getDocs(colRef);
batch.set(countRef, {
count: colSnap.size + 1,
docId: ref.id
});
}
batch.commit();
}
}
删除
async delWithCounter(
ref: DocumentReference<DocumentData>
): Promise<void> {
// counter collection
const counterCol = '_counters';
const col = ref.path.split('/').slice(0, -1).join('/');
const countRef = doc(this.afs, counterCol, col);
const countSnap = await getDoc(countRef);
const batch = writeBatch(this.afs);
// if count exists
batch.delete(ref);
if (countSnap.exists()) {
batch.update(countRef, {
count: increment(-1),
docId: ref.id
});
}
/*
if ((countSnap.data() as any).count == 1) {
batch.delete(countRef);
}*/
batch.commit();
}
请参阅here 了解更多信息...
J
【讨论】:
与许多问题一样,答案是 - 视情况而定。
在前端处理大量数据时应该非常小心。除了让你的前端感觉迟钝之外,Firestore 还 charges you $0.60 per million reads 你做的。
谨慎使用 - 前端用户体验可能会受到影响
在前端处理这个应该没问题,只要你没有对这个返回的数组做太多的逻辑。
db.collection('...').get().then(snap => {
size = snap.size // will return the collection size
});
谨慎使用 - 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
})
最具扩展性的解决方案
FieldValue.increment()
As of April 2019 Firestore now allows incrementing counters, completely atomically, and without reading the data prior. 这可确保我们即使在同时从多个来源更新时也有正确的计数器值(之前使用事务解决),同时还减少了我们执行的数据库读取次数。
通过监听任何文档删除或创建,我们可以添加或删除位于数据库中的计数字段。
查看 Firestore 文档 - Distributed Counters 或者看看 Jeff Delaney 的 Data Aggregation。他的指南对于任何使用 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 字段来获取集合的大小。
【讨论】:
所以我对这个问题的解决方案有点非技术性,不是超级精确,但对我来说已经足够了。
那些是我的文件。因为我有很多(100k+),所以发生了“大数定律”。我可以假设具有以 0、1、2 等开头的 id 的项目的数量或多或少。
所以我要做的是滚动我的列表,直到进入从 1 或 01 开始的 id,这取决于您必须滚动多长时间
? 我们来了。
现在,滚动到现在,我打开检查器,看看我滚动了多少,然后除以单个元素的高度
必须滚动 82000 像素才能获取 id 以 1 开头的项目。单个元素的高度为 32px。
这意味着我有 2500 个 id 以0 开头,所以现在我将它乘以可能的“起始字符”的数量。在 Firebase 中,它可以是 A-Z、a-z、0-9,这意味着它是 24 + 24 + 10 = 58。
这意味着我有 ~~2500*58 所以它在我的收藏中提供了大约 145000 件物品。
总结:你的 firebase 出了什么问题?
【讨论】:
offset & limit 的分页解决方案:public int collectionCount(String collection) {
Integer page = 0;
List<QueryDocumentSnapshot> snaps = new ArrayList<>();
findDocsByPage(collection, page, snaps);
return snaps.size();
}
public void findDocsByPage(String collection, Integer page,
List<QueryDocumentSnapshot> snaps) {
try {
Integer limit = 26000;
FieldPath[] selectedFields = new FieldPath[] { FieldPath.of("id") };
List<QueryDocumentSnapshot> snapshotPage;
snapshotPage = fireStore()
.collection(collection)
.select(selectedFields)
.offset(page * limit)
.limit(limit)
.get().get().getDocuments();
if (snapshotPage.size() > 0) {
snaps.addAll(snapshotPage);
page++;
findDocsByPage(collection, page, snaps);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
findDocsPage是递归查找所有页面的方法
selectedFields 用于优化查询并仅获取 id 字段而不是完整的文档正文
limit每个查询页面的最大大小
page定义分页的初始页面
根据我所做的测试,它适用于最多大约 120k 记录的集合!
【讨论】:
offset(119223) 将收取 119,223 次读取的费用,这可能会非常昂贵如果一直使用。如果您知道startAt(doc) 的文档,那会有所帮助,但通常您没有该信息,或者您不会搜索!
var variable=0
variable=variable+querySnapshot.count
如果你要在字符串变量上使用它
let stringVariable= String(variable)
【讨论】:
在 2020 年,Firebase SDK 中仍不提供此功能,但 Firebase Extensions (Beta) 提供此功能,但设置和使用非常复杂...
合理的方法
Helpers...(创建/删除似乎是多余的,但比 onUpdate 便宜)
export const onCreateCounter = () => async (
change,
context
) => {
const collectionPath = change.ref.parent.path;
const statsDoc = db.doc("counters/" + collectionPath);
const countDoc = {};
countDoc["count"] = admin.firestore.FieldValue.increment(1);
await statsDoc.set(countDoc, { merge: true });
};
export const onDeleteCounter = () => async (
change,
context
) => {
const collectionPath = change.ref.parent.path;
const statsDoc = db.doc("counters/" + collectionPath);
const countDoc = {};
countDoc["count"] = admin.firestore.FieldValue.increment(-1);
await statsDoc.set(countDoc, { merge: true });
};
export interface CounterPath {
watch: string;
name: string;
}
导出的 Firestore 挂钩
export const Counters: CounterPath[] = [
{
name: "count_buildings",
watch: "buildings/{id2}"
},
{
name: "count_buildings_subcollections",
watch: "buildings/{id2}/{id3}/{id4}"
}
];
Counters.forEach(item => {
exports[item.name + '_create'] = functions.firestore
.document(item.watch)
.onCreate(onCreateCounter());
exports[item.name + '_delete'] = functions.firestore
.document(item.watch)
.onDelete(onDeleteCounter());
});
在行动
将跟踪正在构建的根集合和所有子集合。
这里是/counters/根路径下
现在收集计数将自动更新!如果需要计数,只需使用集合路径并在其前面加上 counters 前缀即可。
const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
.doc('counters/' + collectionPath)
.get()
.then(snap => snap.get('count'));
限制
由于此方法使用单个数据库和文档,因此每个计数器受限于 每秒更新 1 次的 Firestore 约束。它最终会保持一致,但在添加/删除大量文档的情况下,计数器将落后于实际收集计数。
【讨论】:
这使用计数来创建数字唯一 ID。在我的使用中,我永远不会递减,即使需要 ID 的 document 被删除。
在需要唯一数值的 collection 创建时
appData,set 和.doc id only
firebase firestore console 中将uniqueNumericIDAmount 设置为0
doc.data().uniqueNumericIDAmount + 1 作为唯一的数字IDappData 集合uniqueNumericIDAmount 更新为firebase.firestore.FieldValue.increment(1)
firebase
.firestore()
.collection("appData")
.doc("only")
.get()
.then(doc => {
var foo = doc.data();
foo.id = doc.id;
// your collection that needs a unique ID
firebase
.firestore()
.collection("uniqueNumericIDs")
.doc(user.uid)// user id in my case
.set({// I use this in login, so this document doesn't
// exist yet, otherwise use update instead of set
phone: this.state.phone,// whatever else you need
uniqueNumericID: foo.uniqueNumericIDAmount + 1
})
.then(() => {
// upon success of new ID, increment uniqueNumericIDAmount
firebase
.firestore()
.collection("appData")
.doc("only")
.update({
uniqueNumericIDAmount: firebase.firestore.FieldValue.increment(
1
)
})
.catch(err => {
console.log(err);
});
})
.catch(err => {
console.log(err);
});
});
【讨论】:
我尝试了很多不同的方法。 最后,我改进了其中一种方法。 首先,您需要创建一个单独的集合并将所有事件保存在那里。 其次,您需要创建一个由时间触发的新 lambda。此 lambda 将统计事件集合中的事件并清除事件文档。 文章中的代码详细信息。 https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca
【讨论】:
据我所知,目前还没有内置解决方案,目前只能在 node sdk 中使用。 如果你有一个
db.collection('someCollection')
你可以使用
.select([fields])
定义您要选择的字段。如果你执行一个空的 select(),你只会得到一个文档引用数组。
示例:
db.collection('someCollection').select().get().then(
(snapshot) => console.log(snapshot.docs.length)
);
此解决方案仅针对下载所有文档的最坏情况进行了优化,不适用于大型集合!
也看看这个:
How to get a count of number of documents in a collection with Cloud Firestore
【讨论】:
select(['_id']) 比 select() 快
解决方法是:
在 firebase 文档中编写一个计数器,每次创建新条目时都会在事务中递增该计数器
您将计数存储在新条目的字段中(即:位置:4)。
然后在该字段上创建一个索引(位置 DESC)。
您可以使用查询进行跳过+限制。Where("position", "
希望这会有所帮助!
【讨论】:
根据上面的一些答案,我花了一段时间才完成这项工作,所以我想我会分享给其他人使用。我希望它有用。
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.countDocumentsChange = functions.firestore.document('library/{categoryId}/documents/{documentId}').onWrite((change, context) => {
const categoryId = context.params.categoryId;
const categoryRef = db.collection('library').doc(categoryId)
let FieldValue = require('firebase-admin').firestore.FieldValue;
if (!change.before.exists) {
// new document created : add one to count
categoryRef.update({numberOfDocs: FieldValue.increment(1)});
console.log("%s numberOfDocs incremented by 1", categoryId);
} else if (change.before.exists && change.after.exists) {
// updating existing document : Do nothing
} else if (!change.after.exists) {
// deleting document : subtract one from count
categoryRef.update({numberOfDocs: FieldValue.increment(-1)});
console.log("%s numberOfDocs decremented by 1", categoryId);
}
return 0;
});
【讨论】:
没有可用的直接选项。你不能做db.collection("CollectionName").count()。
以下是查找集合中文档数的两种方法。
db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})
通过使用上述代码,您的文档读取将等于集合中文档的大小,这就是必须避免使用上述解决方案的原因。
db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})
上面我们创建了一个包含名称counts的文档来存储所有的count信息。您可以通过以下方式更新count文档:-
w.r.t 价格(Document Read = 1)和快速数据检索上述解决方案很好。
【讨论】:
使用admin.firestore.FieldValue.increment 增加一个计数器:
exports.onInstanceCreate = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
.onCreate((snap, context) =>
db.collection('projects').doc(context.params.projectId).update({
instanceCount: admin.firestore.FieldValue.increment(1),
})
);
exports.onInstanceDelete = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
.onDelete((snap, context) =>
db.collection('projects').doc(context.params.projectId).update({
instanceCount: admin.firestore.FieldValue.increment(-1),
})
);
在此示例中,每次将文档添加到 instances 子集合时,我们都会在项目中增加一个 instanceCount 字段。如果该字段尚不存在,它将被创建并递增到 1。
增量在内部是事务性的,但如果您需要比每 1 秒更频繁地增加,则应使用 distributed counter。
通常最好实现 onCreate 和 onDelete 而不是 onWrite,因为您将调用 onWrite 进行更新,这意味着您在不必要的函数调用上花费了更多的钱(如果您更新集合中的文档) .
【讨论】:
小心计算大型集合的文档数量。如果您想为每个集合设置一个预先计算的计数器,那么使用 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;
});
原因是因为每个 Cloud 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 在所有情况下都相同。不幸的是,您的数据库中必须有“事件”集合。
【讨论】:
context.eventId 在多次调用同一个触发器时总是相同的?在我的测试中,它似乎是一致的,但我找不到任何说明这一点的“官方”文档。
我同意@Matthew,如果您执行这样的查询,它将花费很多。
[开发者开始项目前的建议]
由于我们一开始就预见到了这种情况,我们实际上可以用一个文档来做一个集合,即计数器,将所有计数器存储在一个类型为number的字段中。
例如:
对于集合上的每个 CRUD 操作,更新计数器文档:
下次要获取集合的数量时,只需要查询/指向文档字段即可。 [1 次读取操作]
另外,你可以将集合名称存储在数组中,但这会很棘手,firebase中数组的条件如下所示:
// we send this
['a', 'b', 'c', 'd', 'e']
// Firebase stores this
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
// since the keys are numeric and sequential,
// if we query the data, we get this
['a', 'b', 'c', 'd', 'e']
// however, if we then delete a, b, and d,
// they are no longer mostly sequential, so
// we do not get back an array
{2: 'c', 4: 'e'}
所以,如果你不打算删除集合,你实际上可以使用数组来存储集合名称的列表,而不是每次都查询所有集合。
希望对你有帮助!
【讨论】:
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
@Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
int Counter = documentSnapshots.size();
}
});
【讨论】:
最简单的方法是读取“querySnapshot”的大小。
db.collection("cities").get().then(function(querySnapshot) {
console.log(querySnapshot.size);
});
您还可以在“querySnapshot”中读取 docs 数组的长度。
querySnapshot.docs.length;
或者如果“querySnapshot”通过读取空值是空的,这将返回一个布尔值。
querySnapshot.empty;
【讨论】:
db.collection.count(),这真是令人难以置信。考虑只为此而放弃它们
不,目前没有对聚合查询的内置支持。但是,您可以做一些事情。
第一个是documented here。您可以使用事务或云功能来维护汇总信息:
此示例展示了如何使用函数来跟踪子集合中的评分数量以及平均评分。
exports.aggregateRatings = firestore
.document('restaurants/{restId}/ratings/{ratingId}')
.onWrite(event => {
// Get value of the newly added rating
var ratingVal = event.data.get('rating');
// Get a reference to the restaurant
var restRef = db.collection('restaurants').document(event.params.restId);
// Update aggregations in a transaction
return db.transaction(transaction => {
return transaction.get(restRef).then(restDoc => {
// Compute new number of ratings
var newNumRatings = restDoc.data('numRatings') + 1;
// Compute new average rating
var oldRatingTotal = restDoc.data('avgRating') * restDoc.data('numRatings');
var newAvgRating = (oldRatingTotal + ratingVal) / newNumRatings;
// Update restaurant info
return transaction.update(restRef, {
avgRating: newAvgRating,
numRatings: newNumRatings
});
});
});
});
如果您只想不频繁地统计文档,jbb 提到的解决方案也很有用。确保使用select() 语句来避免下载每个文档的所有内容(当您只需要计数时,这会占用大量带宽)。 select() 目前仅在服务器 SDK 中可用,因此该解决方案无法在移动应用中运行。
【讨论】: