Firebase firestore集合计数

Gui*_*tro 102 firebase google-cloud-firestore

是否可以使用新的firebase数据库firestore计算集合中有多少项?

如果是这样,我该怎么做?

Mat*_*lin 137

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

在前端处理大量数据时应该非常小心.除了让您的前端感觉迟钝之外,Firestore还会向您收取每百万读取0.60美分的费用.


小集合(少于100个文件)

谨慎使用 - 前端用户体验可能会受到影响

只要你没有对这个返回的数组做太多的逻辑,在前端处理这个就应该没问题.

db.collection('...').get().then(snap => {
   size = snap.size // will return the collection size
});
Run Code Online (Sandbox Code Playgroud)

中等收藏(100至1000份文件)

谨慎使用 - Firestore读取调用可能会花费很多

在前端处理此问题是不可行的,因为它有太多可能减慢用户系统的速度.我们应该处理这个逻辑服务器端,只返回大小.

这种方法的缺点是你仍在调用firestore读取(等于你的集合的大小),从长远来看,这可能最终导致你的成本超出预期.

云功能:

...
db.collection('...').get().then(snap => {
    res.status(200).send({length: snap.size});
});
Run Code Online (Sandbox Code Playgroud)

前端:

yourHttpClient.post(yourCloudFunctionUrl).toPromise().then(snap => {
     size = snap.length // will return the collection size
})
Run Code Online (Sandbox Code Playgroud)

大量收藏(1000多份文件)

大多数可扩展的解

通过侦听任何文档删除或创建,我们可以添加到数据库中的计数字段或从中删除.

请参阅firestore文档 - 分布式计数器 或查看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;
});
Run Code Online (Sandbox Code Playgroud)

现在在前端,您可以查询此numberOfDocs字段以获取集合的大小.

  • 大型系列的绝佳解决方案!我想补充一点,实现者应该将读写包装在`firestore.runTransaction {...}`块中.这解决了访问`numberOfDocs`的并发问题. (13认同)
  • 大型馆藏的解决方案不是幂等的,并且无法在任何规模上使用。确保Firestore文档触发器至少运行一次,但可以运行多次。发生这种情况时,即使在事务中维护更新也可能会运行多次,这会给您一个错误的数字。尝试此操作时,一次遇到少于十二个文档创建的问题。 (4认同)
  • 这些方法使用重新计算记录数.如果您使用计数器并使用事务递增计数器,如果没有增加成本和云功能的需要,是否会达不到相同的结果? (2认同)
  • 嗨@TymPollack。我注意到使用云触发器存在一些不一致的行为。您是否有机会将我链接到文章或论坛,以解释您所经历的行为? (2认同)
  • @cmprogram,当使用 db.collection('...') 时,您正在读取整个集合和数据...所以当您不需要数据时,您是对的 - 您可以轻松请求一个列表集合 ID(不是集合文档数据),它算作一次读取。 (2认同)
  • @MatthewMullin 你能提供一个前端代码示例来访问 numberOfDocs 字段吗?我不明白该字段是否在集合引用中或在另一个集合(例如“计数器”)中。谢谢!! (2认同)

Joh*_*ika 32

聚合计数查询刚刚在 Firestore 中作为预览版发布。

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

摘抄:

[开发者预览] Count() 函数:借助 Firstore 中的新计数函数 [原文如此],您现在可以在运行查询或从集合中读取时获取匹配文档的计数,而无需加载实际文档,这可以节省您的时间很多时间。

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

在此输入图像描述

在问答过程中,有人询问聚合查询的定价,Firebase 团队提供的答案是,它将花费读取价格的千分之一(四舍五入到最接近的读取,有关更多详细信息,请参阅下面的评论),但会计算属于聚合的所有记录。


Omp*_*pel 20

最简单的方法是读取"querySnapshot"的大小.

db.collection("cities").get().then(function(querySnapshot) {      
    console.log(querySnapshot.size); 
});
Run Code Online (Sandbox Code Playgroud)

您还可以在"querySnapshot"中读取docs数组的长度.

querySnapshot.docs.length;
Run Code Online (Sandbox Code Playgroud)

或者,如果"querySnapshot"为空,则读取空值,该值将返回布尔值.

querySnapshot.empty;
Run Code Online (Sandbox Code Playgroud)

  • 请注意,每个文档"花费"一个读取.因此,如果您通过这种方式计算100个项目,则需要支付100次读取费用! (54认同)
  • Firebase Firestore没有`db.collection.count()`这真是令人难以置信.只想为此而放弃它们 (51认同)
  • 这会读取内存中的所有文件!祝大型数据集好运...... (4认同)
  • 特别是对于大型馆藏,向我们收取费用就好像我们实际下载并使用了所有文档一样,是不公平的。计数表(集合)就是这样的基本功能。考虑到他们的定价模式以及Firestore于2017年推出,令人难以置信的是Google没有提供替代方法来获取馆藏的大小。在不执行之前,至少应避免为此付费。 (3认同)

jbb*_*jbb 16

据我所知,没有针对此的内置解决方案,它现在只能在节点sdk中使用.如果你有

db.collection( 'someCollection')

您可以使用

.选择([字段])

定义要选择的字段.如果你执行一个空的select(),你将得到一个文档引用数组.

例:

db.collection('someCollection').select().get().then( (snapshot) => console.log(snapshot.docs.length) );

此解决方案仅针对下载所有文档的最坏情况进行了优化,并且无法在大型集合上进行扩展!

还要看一下:
如何使用Cloud Firestore计算集合中的文档数量


Ben*_*ing 13

到 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;
}

Run Code Online (Sandbox Code Playgroud)

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

Run Code Online (Sandbox Code Playgroud)

在行动

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

在此处输入图片说明

这里/counters/根路径下

在此处输入图片说明

现在收集计数将自动更新并最终更新!如果您需要计数,只需使用集合路径并以counters.

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

限制

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

  • @Pooja 该限制是错误的,因为它指的是_分布式_计数器,上述解决方案是_不是_分布式的。 (2认同)

Ros*_*des 10

截至 2022 年 10 月,Firestorecount()在客户端 SDK 上引入了一种方法。现在您无需下载即可计算查询。

对于 1000 个文档,将收取您阅读 1 个文档的费用。

网页 (v9)

Firebase 9.11.0中引入:

const collectionRef = collection(db, "cities");
const snapshot = await getCountFromServer(collectionRef);
console.log('count: ', snapshot.data().count);
Run Code Online (Sandbox Code Playgroud)

网页V8

无法使用。

节点(管理员)

const collectionRef = db.collection('cities');
const snapshot = await collectionRef.count().get();
console.log(snapshot.data().count);
Run Code Online (Sandbox Code Playgroud)

安卓(科特林)

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())
    }
}
Run Code Online (Sandbox Code Playgroud)

Apple 平台 (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)
}
Run Code Online (Sandbox Code Playgroud)


Fer*_*dés 8

小心计算大型馆藏的文件数量.如果你想为每个集合安装一个预先计算的计数器,那么它与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;
});
Run Code Online (Sandbox Code Playgroud)

原因是因为每个云防火墙触发器都必须是幂等的,因为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;
    });
Run Code Online (Sandbox Code Playgroud)

用例在这里:

/**
 * 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'));
Run Code Online (Sandbox Code Playgroud)

如您所见,防止多次执行的关键是上下文对象中名为eventId的属性.如果对同一事件多次处理该函数,则事件id在所有情况下都是相同的.不幸的是,您必须在数据库中拥有"events"集合.

  • 他们这样说,好像此行为将在1.0版本中修复。Amazon AWS功能也遇到相同的问题。计数字段这样简单的事情变得复杂且昂贵。 (2认同)
  • 因此,使用了一段时间后,我发现,虽然此解决方案确实可以一次写入就可以工作,但这很棒,如果一次写入多个文档并尝试更新相同计数文档的触发器太多,则可以从Firestore获取竞争错误。您是否遇到过这些问题,并且如何解决?(错误:10已终止:对这些文档的争用太多。请重试。) (2认同)

Jon*_*han 6

11/20 更新

我创建了一个 npm 包,以便轻松访问计数器功能:https://code.build/p/9DicAmrnRoK4uk62Hw1bEV/firestore-counters


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

唯一的例外是当每秒执行如此多的写入时,它会减慢您的速度。一个例子是热门帖子上的点赞。例如,在博客文章中这样做就太过分了,而且会花费更多。我建议在这种情况下使用分片创建一个单独的函数: 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);
        });;
    }
}
Run Code Online (Sandbox Code Playgroud)

它处理事件、增量和事务。这样做的好处是,如果您不确定文档的准确性(可能仍处于测试阶段),您可以删除计数器,让它在下一个触发时自动将它们添加起来。是的,这是有成本的,所以不要删除它。

同样的事情来获得计数:

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const colSnap = await db.doc('_counters/' + collectionPath).get();
const count = colSnap.get('count');
Run Code Online (Sandbox Code Playgroud)

此外,您可能希望创建一个 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;
    });
Run Code Online (Sandbox Code Playgroud)

最后,不要忘记保护firestore.rules中的集合:

match /_counters/{document} {
  allow read;
  allow write: if false;
}
match /_events/{document} {
  allow read, write: if false;
}
Run Code Online (Sandbox Code Playgroud)

更新:查询

如果您还想自动执行查询计数,请添加到我的其他答案中,您可以在云函数中使用此修改后的代码:

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

Run Code Online (Sandbox Code Playgroud)

这将自动更新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;
}
Run Code Online (Sandbox Code Playgroud)

我还应该警告您,通用函数将在每个 onWrite 调用期间运行。仅在特定集合的 onCreate 和 onDelete 实例上运行该函数可能会更便宜。就像我们正在使用的 noSQL 数据库一样,重复的代码和数据可以为您节省金钱。


Sam*_*ern 5

不,目前没有对聚合查询的内置支持。但是,您可以做一些事情。

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

此示例说明如何使用函数来跟踪子集合中的评分数量以及平均评分。

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
        });
      });
    });
});
Run Code Online (Sandbox Code Playgroud)

如果您只想不经常计算文档,jbb 提到的解决方案也很有用。确保使用该select()语句避免下载每个文档的所有内容(当您只需要计数时,这会占用大量带宽)。 select()目前仅在服务器 SDK 中可用,因此该解决方案不适用于移动应用程序。


Ang*_*Tay 5

我同意@Matthew,如果执行这样的查询,它将花费很多

[开发者的建议在开始他们的项目之前]

由于我们在一开始就预见到了这种情况,因此我们实际上可以进行一个收集,即使用文档创建计数器,以将所有计数器存储在type为的字段中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'}
Run Code Online (Sandbox Code Playgroud)

因此,如果您不打算删除collection,那么实际上可以使用array来存储collection名称列表,而不是每次都查询所有collection。

希望能帮助到你!

  • 对于一个小收藏来说,也许吧。但请记住,Firestore 文档大小限制为 ~1MB,如果集合中的文档 ID 是自动生成的(20 字节),那么您只能在保存数组的文档之前存储 ~52,425 个文档 ID太大了。我想作为一种解决方法,您可以每 50,000 个元素创建一个新文档,但维护这些数组将完全难以管理。此外,随着文档大小的增长,读取和更新将花费更长的时间,这最终将导致对其进行的任何其他操作超时。 (2认同)

Dom*_*nic 5

使用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),
    })
  );
Run Code Online (Sandbox Code Playgroud)

在此示例instanceCount中,每次将文档添加到instances子集合时,我们都会增加项目中的一个字段。如果该字段尚不存在,它将被创建并增加到 1。

增量在内部是事务性的,但如果您需要比每 1 秒更频繁地增加增量,则应使用分布式计数器

这往往是最好的实现onCreateonDelete而不是onWrite因为你叫onWrite更新,这意味着你是在不必要的函数调用花更多的钱(如果您更新集合中的文档)。


小智 5

没有直接选项可用。你不能做db.collection("CollectionName").count()。以下是您可以找到集合中文档数量计数的两种方法。

1 :- 获取集合中的所有文档,然后获取它的大小。(不是最好的解决方案)

db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})
Run Code Online (Sandbox Code Playgroud)

通过使用上面的代码,您的文档读取将等于集合中文档的大小,这就是为什么必须避免使用上述解决方案的原因。

2:- 在您的集合中创建一个单独的文档,它将存储集合中文档的数量。(最佳解决方案)

db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})
Run Code Online (Sandbox Code Playgroud)

上面我们创建了一个名为counts的文档来存储所有的count信息。您可以通过以下方式更新count文档:-

  • 在文档计数上创建 firestore 触发器
  • 创建新文档时增加计数文档的计数属性。
  • 删除文档时,减少计数文档的计数属性。

wrt 价格(文档读取 = 1)和快速数据检索上述解决方案很好。