在Firestore中,如何获取集合中的文档总数?
例如,如果我有
/people
/123456
/name - 'John'
/456789
/name - 'Jane'
我想查询我有多少人并获得2。
我可以对/ people进行查询然后获取返回结果的长度,但这似乎是浪费,特别是因为我将在更大的数据集上执行此操作。
答案 0 :(得分:32)
您目前有3个选项:
这基本上就是你提到的方法。从集合中选择全部并在客户端计数。这适用于小型数据集,但如果数据集较大,则显然不起作用。
使用此方法,您可以使用云功能更新计数器,以便从集合中添加和删除。
这适用于任何数据集大小,只要添加/删除仅以小于或等于每秒1的速率发生。这为您提供了一个单独的文档,可以立即为您提供几乎当前的计数。
如果需要每秒超过1次,则需要实施distributed counters per our documentation。
在客户端中,您可以在添加或删除文档的同时更新计数器,而不是使用云功能。这意味着计数器也是最新的,但您需要确保在添加或删除文档的任何位置包含此逻辑。
与选项2一样,如果要超过每秒
,则需要实现分布式计数器答案 1 :(得分:3)
如果你使用AngulareFire2,你可以这样做(假设你的构造函数中注入了private afs: AngularFirestore
):
this.afs.collection(myCollection).valueChanges().subscribe( values => console.log(values.length));
此处,values
是myCollection
中所有项目的数组。您不需要元数据,因此您可以直接使用valueChanges()
方法。
答案 2 :(得分:2)
聚合是可行的方法(firebase函数看起来像更新这些聚合的推荐方式,因为客户端向您不希望公开的用户公开信息)https://firebase.google.com/docs/firestore/solutions/aggregation
另一种方式(不推荐)对大型列表不利并涉及下载整个列表:res.size就像这个例子:
db.collection('products').get().then(res => console.log(res.size))
答案 3 :(得分:1)
请小心计算具有云功能的大型馆藏的文档数量。如果您希望每个集合都有一个预先计算的计数器,那么对于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;
});
原因是因为每个云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在所有情况下均相同。不幸的是,您必须在数据库中具有“事件”集合。
答案 4 :(得分:0)
关注Dan回答:您可以在数据库中使用单独的计数器并使用云功能来维护它。 (写作时尽力而为)
2018-01-25 21:28:13.597 Info: {"firstName":"Hugo", "lastName":"Koopmans", "userName":"xxxxxx", "email":"xxx@xxx.com", "password":"xxxxx", "activationCode":"4db600e771f85073a03ed16371ba39ad3b408852", "active":false, "id":"xxxxxx", "devices":["xxxxxxx"]}
2018-01-25 21:29:47.559 Notice: SVC-CANCELED: xdmp.documentInsert("/users/952187962xx.json", ObjectNode({"firstName":"Hugo", "lastName":"Koopmans", "userName":"xxxxx", "email":"...", ...}), Sequence({...XDMP-CANCELED: Canceled request "/v1/users/passwordreset"..., {...XDMP-CANCELED: Canceled request "/v1/users/passwordreset"...)) -- Canceled
2018-01-25 21:29:47.559 Notice:+in xdmp:eval("declareUpdate(); xdmp.documentInsert(uri, doc,[xdmp.permission(u...", {uri:"/users/952187962xxxxx.json", userName:"xxxxx", doc:{firstName:"Hugo", lastName:"Koopmans", userName:"xxxxx", ...}}, {isolation:"different-transaction", userId:"9521879xxxx"}) [javascript]
2018-01-25 21:29:47.559 Notice:+in /v1/users.sjs [javascript]
2018-01-25 21:29:47.559 Notice:+in /v1/users.sjs [javascript]
2018-01-25 21:29:48.535 Notice: XDMP-CANCELED: Canceled request "/MarkLogic/rest-api/error-handler.xqy"
2018-01-25 21:29:48.535 Notice:+in /MarkLogic/manage/endpoints/databases/partitions/partitions.xqy, at 184:9, in pa:get-partition() [1.0-ml]
2018-01-25 21:29:48.535 Notice: XDMP-CANCELED: Canceled request "/MarkLogic/rest-api/error-handler.xqy"
2018-01-25 21:29:48.535 Notice:+in /MarkLogic/rest-api/error-handler.xqy [1.0-ml]
此代码显示了如何执行此操作的完整示例: https://gist.github.com/saintplay/3f965e0aea933a1129cc2c9a823e74d7
答案 5 :(得分:0)
请检查以下我在另一个线程上找到的答案。您的数量应该是原子的。在这种情况下,必须使用 FieldValue.increment()函数。
答案 6 :(得分:0)
我创建了一个NPM包来处理所有计数器:
首先将模块安装在您的功能目录中:
npm i adv-firestore-functions
然后像这样使用它:
import { eventExists, colCounter } from 'adv-firestore-functions';
functions.firestore
.document('posts/{docId}')
.onWrite(async (change: any, context: any) => {
// don't run if repeated function
if (await eventExists(context)) {
return null;
}
await colCounter(change, context);
}
它处理事件以及其他所有事情。
如果要使其成为所有功能的通用计数器:
import { eventExists, colCounter } from 'adv-firestore-functions';
functions.firestore
.document('{colId}/{docId}')
.onWrite(async (change: any, context: any) => {
const colId = context.params.colId;
// don't run if repeated function
if (await eventExists(context) || colId.startsWith('_')) {
return null;
}
await colCounter(change, context);
}
不要忘记您的规则:
match /_counters/{document} {
allow read;
allow write: if false;
}
当然可以这样访问它:
const collectionPath = 'path/to/collection';
const colSnap = await db.doc('_counters/' + collectionPath).get();
const count = colSnap.get('count');
了解更多:https://fireblog.io/blog/post/firestore-counters
GitHub:https://github.com/jdgamble555/adv-firestore-functions
答案 7 :(得分:-1)
使用Transaction更新数据库写入成功侦听器中的计数。
FirebaseFirestore.getInstance().runTransaction(new Transaction.Function<Long>() {
@Nullable
@Override
public Long apply(@NonNull Transaction transaction) throws FirebaseFirestoreException {
DocumentSnapshot snapshot = transaction
.get(pRefs.postRef(forumHelper.getPost_id()));
long newCount;
if (b) {
newCount = snapshot.getLong(kMap.like_count) + 1;
} else {
newCount = snapshot.getLong(kMap.like_count) - 1;
}
transaction.update(pRefs.postRef(forumHelper.getPost_id()),
kMap.like_count, newCount);
return newCount;
}
});