Cloud Firestore收集计数

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

如果是,我该怎么做?

251505 次浏览
据我所知,没有内置的解决方案,现在只有在节点sdk中才有可能。 如果你有

db.collection('someCollection')

你可以使用

.select([fields])

定义要选择的字段。如果执行空select(),则只会得到一个文档引用数组。

例子:

< p > <代码> db.collection (someCollection) .select () . get () ( (快照)=比;console.log (snapshot.docs.length) 代码);< / > < / p >

此解决方案只是针对下载所有文档的最坏情况的优化,并且不能扩展到大型集合!

还有这个:
如何获得在一个集合与云Firestore的文件的数量计数 < / p >

不,目前还没有内置的聚合查询支持。然而,有几件事你可以做。

第一个是记录在这里。您可以使用事务或云函数来维护聚合信息:

这个例子展示了如何使用一个函数来跟踪子集合中的评级数量,以及平均评级。

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中可用,所以这个解决方案不会在移动应用程序中工作。

最简单的方法是读取“querySnapshot”的大小。

db.collection("cities").get().then(function(querySnapshot) {
console.log(querySnapshot.size);
});

你也可以在querySnapshot中读取文档数组的长度。

querySnapshot.docs.length;

或者如果“querySnapshot”为空,则读取空值,将返回一个布尔值。

querySnapshot.empty;

和许多问题一样,答案是- 这取决于

在前端处理大量数据时应该非常小心。除了让你的前端感觉迟钝之外,Firestore也收费你$0.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文档- 分布式计数器 或者看看Jeff Delaney的数据聚合。他的指南对于任何使用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字段来获得集合的大小。

firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
@Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {


int Counter = documentSnapshots.size();


}
});

我同意@Matthew,如果你执行这样的查询,它将成本很高

[开发者开始项目前的建议]

由于我们在一开始就预见到了这种情况,我们实际上可以用一个文档创建一个集合,即计数器,将所有计数器存储在类型为number的字段中。

例如:

对于集合上的每个CRUD操作,更新计数器文档:

  1. 当你创建一个新的集合/子集合:(在计数器中+1) 【1写操作】
  2. 当你删除一个集合/子集合时:(计数器中的-1) 【1写操作】
  3. 当你更新一个现有的集合/子集合时,对计数器文档:(0)不做任何操作
  4. 当你一个现有的集合/子集合时,对计数器文档:(0)不做任何操作

下一次,当您想要获得集合的数量时,您只需要查询/指向文档字段。[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'}

所以,如果你不打算删除集合,你实际上可以使用数组来存储集合名称的列表而不是每次都查询所有的集合。

希望能有所帮助!

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

使用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秒增加更频繁的增量,你应该使用分布式计数器

通常最好实现onCreateonDelete,而不是onWrite,因为你会调用onWrite进行更新,这意味着你在不必要的函数调用上花费了更多的钱(如果你更新了你的集合中的文档)。

没有直接的选项。你不能做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)和快速数据检索上述解决方案是很好的。

根据上面的一些答案,我花了一段时间才让它工作,所以我想把它分享给其他人使用。希望对大家有用。

'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;
});

解决办法是:

在firebase文档中编写一个计数器,每次创建新条目时都在事务中增加计数器

您将计数存储在新条目的字段中(即:position: 4)。

然后在该字段上创建一个索引(position DESC)。

您可以对查询执行跳过+限制操作。Where("position", "<" x).OrderBy("position", DESC)

希望这能有所帮助!

在2020年,这仍然不可用在Firebase SDK中,但它在Firebase扩展(测试版)中可用,然而它的设置和使用相当复杂…

合理的方法

帮手……(创建/删除似乎是多余的,但比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());
});


在行动

建筑物集合和所有子集合将被跟踪。

enter image description here

在这里的/counters/根路径下

enter image description here

现在收集计数将自动更新,最终!如果需要计数,只需使用收集路径并以counters作为前缀。

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
.doc('counters/' + collectionPath)
.get()
.then(snap => snap.get('count'));

限制

由于此方法使用单个数据库和文档,因此对于每个计数器,它仅限于每秒1次更新的Firestore约束。它最终将是一致的,但在添加/删除大量文档的情况下,计数器将落后于实际收集计数。

更新11/20

为了方便访问计数器函数,我创建了一个npm包:https://code.build/p/9DicAmrnRoK4uk62Hw1bEV/firestore-counters


我使用所有这些想法创建了一个通用函数来处理所有的计数器情况(查询除外)。

唯一的例外是当做这么多写一秒,它 放慢你的速度。一个例子是趋势帖子上的喜欢。它是 例如,在一篇博客文章上写得太多,会让你付出更多的代价。我 建议在这种情况下使用shards创建一个单独的函数: https://firebase.google.com/docs/firestore/solutions/counters < / p >

// 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 < / p >

这是< >强未经考验的< / >强,但应该做一些调整:

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数据库,重复的代码和数据可以节省你 钱。< / p >
我尝试了很多不同的方法。 最后,我改进了其中一种方法。 首先,您需要创建一个单独的集合并保存其中的所有事件。 其次,您需要创建一个由时间触发的新lambda。此lambda将计数事件集合中的事件并清除事件文档。 代码细节见文章。 https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca < / p >

除了上面的npm包adv-firestore-functions,你还可以使用firestore规则强制一个好的计数器:

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();
}

更多信息见在这里

J

这使用计数来创建数字唯一ID。在我的使用中,我永远不会减量,即使需要ID的document被删除。

在需要唯一数值的collection创建时

  1. 用一个文档指定集合appData,用.doc id only指定集合set
  2. uniqueNumericIDAmount设置为0
  3. 使用doc.data().uniqueNumericIDAmount + 1作为唯一的数字id
  4. firebase.firestore.FieldValue.increment(1)更新appData集合uniqueNumericIDAmount
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);
});
});
var variable=0
variable=variable+querySnapshot.count

那么如果你要在String变量上使用它,那么

let stringVariable= String(variable)

使用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定义用于分页的初始页面

从测试中,我所做的收集工作得很好,大约120 k的记录!

所以我对这个问题的解决方案有点非技术,不是特别精确,但对我来说已经足够好了。

enter image description here

这些是我的文件。因为我有很多(10万+),所以有“大数定律”发生。我可以假设,id以0、1、2开头的项目数量或少或多相等。

所以我要做的是滚动列表直到id从1开始,或者从01开始,这取决于你需要滚动多长时间

enter image description here

👆我们在这里。

现在,滚动到目前为止,我打开检查器,看看我滚动了多少,然后除以单个元素的高度

enter image description here

必须滚动82000px才能获得id以1开头的项目。单个元素的高度为32px。

这意味着我有2500个id以0开头,所以现在我将它乘以可能的“起始字符”的数量。在firebase中,它可以是A-Z, A-Z, 0-9,这意味着24 + 24 + 10 = 58。

这意味着我有~~2500*58,所以它给了我大约145000件藏品。

总结:你的firebase出了什么问题?

其中一个快速省钱的技巧是:-

创建一个doc并在firestore中存储一个'count'变量,当用户在集合中添加新的doc时,增加该变量,当用户删除一个doc时,减少变量。如。 updateDoc(doc(db, "Count_collection", "Count_Doc"), {count: increment(1)}); < / p >

注意:使用(-1)减少count,(1)增加count

如何节省金钱和时间:-

  1. 你(firebase)不需要循环通过集合,浏览器也不需要加载整个集合来计数文档的数量。
  2. 所有的计数都保存在一个doc中,只有一个名为“count”的变量。因此,使用的数据小于1kb,并且它只在firebase firestore中使用1次读取。

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

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

裁判:

自版本9.11.0以来,有一个新的内置函数称为getCountFromServer (),它在不实际下载文档的情况下获取结果集中的文档数量。

https://firebase.google.com/docs/reference/js/firestore_#getcountfromserver

截至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,节点(管理)

不可用。

Android(芬兰湾的科特林)

在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)
}
此功能现在在FireStore中得到支持,尽管是Beta版本。 下面是Firebase官方文档

. xml文件

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

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

摘录:

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

.

.

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

enter image description here

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

有了新版本的Firebase,你现在可以运行聚合查询了! 只是写< / p >
.count().get();

在您的询问之后。

目前,firebase只允许服务器端计数,如下所示

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

请不要,这是为nodeJS

Firebase/Firestore中的新功能提供了集合中的文档计数:

请参阅本线程以了解如何实现它,并提供了一个示例。

如何计算在一个集合中的Firebase Firestore与WHERE查询在react.js .js

根据这个文档, 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;
}