是否可以使用新的Firebase数据库Cloud Firestore来计算一个集合有多少项?
如果是,我该怎么做?
是否可以使用新的Firebase数据库Cloud Firestore来计算一个集合有多少项?
如果是,我该怎么做?
当前回答
使用带有偏移量和限制的分页的解决方案:
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字段而不是整个文档 限制每个查询页面的最大大小 页面定义用于分页的初始页面
从我做的测试来看,它可以很好地收集大约120k条记录!
其他回答
Firebase/Firestore中的新功能提供了集合中的文档计数:
请参阅本线程以了解如何实现它,并提供了一个示例。
如何计算在一个集合中的Firebase Firestore与WHERE查询在react.js文档的数量
更新11/20
为了方便访问计数器函数,我创建了一个npm包:https://code.build/p/9DicAmrnRoK4uk62Hw1bEV/firestore-counters
我使用所有这些想法创建了一个通用函数来处理所有的计数器情况(查询除外)。
唯一的例外是当一秒钟写这么多的时候,它 放慢你的速度。一个例子就是热门帖子上的点赞。它是 例如,在一篇博客文章上写得太多,会让你付出更多的代价。我 建议在这种情况下使用shards创建一个单独的函数: 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中的集合。
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数据库,重复的代码和数据可以节省你 钱。
没有直接的选择。不能执行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)和快速数据检索上述解决方案是很好的。
在2020年,Firebase SDK中还没有这个功能,但Firebase扩展(Beta)中有,不过设置和使用起来相当复杂……
合理的方法
帮手……(创建/删除似乎是多余的,但比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;
}
出口消防钩
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/ root路径下
现在收集计数将自动更新,最终!如果需要计数,只需使用收集路径并在其前面加上计数器即可。
const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
.doc('counters/' + collectionPath)
.get()
.then(snap => snap.get('count'));
限制
由于此方法使用单个数据库和文档,因此每个计数器的Firestore约束为每秒更新1次。它最终将是一致的,但在添加/删除大量文档的情况下,计数器将落后于实际收集计数。
Firestore引入了一个新的query .count(),它获取查询的计数而不获取文档。
这将允许简单地查询所有集合项并获得该查询的计数。
Ref:
Firebase 10 iOS SDK [JS SDK PR] (https://github.com/firebase/firebase-js-sdk/pull/6608)