Cloud Firestoreコレクション数


回答:


188

更新(2019年4月)-FieldValue.increment(大規模なコレクションソリューションを参照)


多くの質問と同様に、答えは-場合によります

フロントエンドで大量のデータを処理する場合は、十分に注意する必要があります。Firestoreは、フロントエンドの反応が鈍いことに加えて100万回の読み取りごとに$ 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 docs- Distributed Countersを参照する か、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フィールドをクエリして、コレクションのサイズを取得できます。


17
大規模なコレクションに最適なソリューションです。実装者が読み取りと書き込みをfirestore.runTransaction { ... }ブロックでラップする必要があることを追加したいと思います。これにより、へのアクセスに関する同時実行の問題が修正されますnumberOfDocs
efemoney

2
これらのメソッドは、レコード数の再カウントを使用しています。カウンターを使用し、トランザクションを使用してカウンターをインクリメントする場合、追加のコストとクラウド機能の必要性なしに同じ結果が得られませんか?
user3836415

10
大規模なコレクションのソリューションはべき等ではなく、どの規模でも機能しません。Firestoreドキュメントトリガーは少なくとも1回実行されることが保証されていますが、複数回実行される場合があります。これが発生すると、トランザクション内で更新を維持したとしても、複数回実行される可能性があり、誤った数値が返されます。これを試してみたところ、一度に12個未満のドキュメントが作成されるという問題に遭遇しました。
Tym Pollack

2
こんにちは@TymPollack。クラウドトリガーを使用した動作に一貫性がないことに気付きました。あなたが経験した行動を説明するために私や記事やフォーラムにリンクできる可能性はありますか?
マシューマリン

2
@cmprogram db.collection( '...')...を使用するときにコレクション全体とデータを読み取っているので、データが必要ない場合は正しい-簡単にリストのリストを要求できますコレクションID(コレクションドキュメントデータではない)であり、1回の読み取りとしてカウントされます。
atereshkov

24

これを行う最も簡単な方法は、「querySnapshot」のサイズを読み取ることです。

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

「querySnapshot」内のdocs配列の長さを読み取ることもできます。

querySnapshot.docs.length;

または、「querySnapshot」が空の値を読み取って空の場合、ブール値が返されます。

querySnapshot.empty;

73
各ドキュメントは1回の読み取りに「コスト」がかかることに注意してください。したがって、コレクション内の100個のアイテムをこのようにカウントすると、100回の読み取りに対して課金されます。
Georg、

正解ですが、コレクション内のドキュメントの数を合計する方法は他にありません。また、すでにコレクションをフェッチしている場合、「docs」配列を読み取るためにフェッチする必要はないため、読み取りに「コスト」はかかりません。
Ompel 2018年

5
これにより、メモリ内のすべてのドキュメントが読み取られます。大規模なデータセットの場合は、幸運を祈ります...
Dan Dascalescu

84
これは、Firebase Firestoreに一種のがないことは本当に信じられないことですdb.collection.count()。これだけのためにそれらを落とすことを考えています
Blue Bot

8
特に大規模なコレクションの場合、実際にすべてのドキュメントをダウンロードして使用したかのように請求するのは不公平です。テーブル(コレクション)のカウントは、このような基本的な機能です。彼らの価格モデルと2017年にFirestoreがリリースされたことを考えると、Googleがコレクションのサイズを取得する別の方法を提供していないことは驚くべきことです。彼らがそれを実装しない限り、彼らは少なくともそれを請求することを避けるべきです。
nibbana

23

私の知る限り、このための組み込みソリューションはなく、現在はノードSDKでのみ可能です。あなたが持っている場合

db.collection('someCollection')

あなたは使うことができます

.select([fields])

選択するフィールドを定義します。空のselect()を実行すると、ドキュメント参照の配列が取得されます。

例:

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

このソリューションは、すべてのドキュメントをダウンロードするという最悪の場合にのみ最適化されており、大規模なコレクションには対応していません。

これもご覧ください:
Cloud Firestoreを使用してコレクション内のドキュメント数を取得する方法


私の経験でselect(['_id'])は、より高速ですselect()
JAnton

13

大規模なコレクションの場合は、ドキュメントの数を数えるのに注意してください。すべてのコレクションに対して事前に計算されたカウンターが必要な場合は、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はすべての場合で同じになります。残念ながら、データベースには「イベント」コレクションが必要です。


2
彼らは、この動作が1.0リリースで修正されるようにこれを表現しています。Amazon AWS関数にも同じ問題があります。フィールドを数えるほど単純なものは複雑になり、高価になります。
MarcG 2018年

それはより良い解決策のように思われるので、今これを試すつもりです。これまでに戻ってイベントコレクションを削除しますか?日付フィールドを追加し、データセットを小さく保つために、1日以上古いものをパージすることを考えていました(おそらく1mil +イベント/日)。FSでそれを行う簡単な方法がない限り... FSを数か月しか使用していませんでした。
Tym Pollack

1
context.eventId同じトリガーの複数の呼び出しで常に同じであることを確認できますか?私のテストではそれは一貫しているように見えますが、これを述べている「公式」のドキュメントを見つけることができません。
Mike McLin

2
したがって、しばらく使用した後、このソリューションは1回の書き込みで機能しますが、一度に複数のドキュメントが書き込まれ、同じカウントのドキュメントを更新しようとするとトリガーが多すぎる場合は、これが素晴らしいことを発見しました。ファイアストアから競合エラーを取得します。あなたはそれらに遭遇しましたか、そしてどのようにそれを回避しましたか?(エラー:10異常終了:これらのドキュメントの競合が多すぎます。もう一度やり直してください。)
Tym Pollack

1
@TymPollackによる分散カウンターでのドキュメントの書き込みは、毎秒約1回の更新に制限されています
ジェイミー

8

2020年には、Firebase SDKではまだ使用できませんが、Firebase Extensions(ベータ)では使用できますが、セットアップと使用はかなり複雑です...

合理的なアプローチ

ヘルパー...(作成/削除は冗長に見えますが、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秒あたり1つのドキュメントの更新」を受けませんか?
Ayyappa

はい。ただし、最終的には一貫性があります。つまり、コレクション数は最終的に実際のコレクション数と一致します。これは実装する最も簡単なソリューションであり、多くの場合、カウントのわずかな遅れは許容されます。
ベンワインディング

7

@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'}

したがって、コレクションを削除しない場合は、毎回すべてのコレクションをクエリする代わりに、実際に配列を使用してコレクション名のリストを保存できます。

それが役に立てば幸い!


小さなコレクションの場合は、多分。ただし、Firestoreのドキュメントのサイズ制限は最大1MBであり、コレクション内のドキュメントIDが自動生成された場合(20バイト)、配列を保持しているドキュメントの前に最大52,425しか保存できないことに注意してください。大きすぎます。回避策として、50,000要素ごとに新しいドキュメントを作成することはできますが、それらの配列を維持することは完全に管理できなくなります。さらに、ドキュメントのサイズが大きくなると、読み取りと更新に時間がかかり、その結果、他の操作が競合してタイムアウトになります。
Tym Pollack

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

jbbが言及した解決策は、ドキュメントをたまにしか数えたくない場合にも役立ちます。select()ステートメントを使用して、各ドキュメントをすべてダウンロードしないようにしてください(カウントのみが必要な場合は、帯域幅が大量になります)。 select()現時点ではサーバーSDKでのみ利用できるため、モバイルアプリではソリューションは機能しません。


1
このソリューションはべき等ではないため、トリガーが複数回発生すると、評価の数と平均が失われます。
Tym Pollack

4

直接利用できるオプションはありません。あなたはできませんdb.collection("CollectionName").count()。以下は、コレクション内のドキュメント数のカウントを確認する2つの方法です。

1:-コレクション内のすべてのドキュメントを取得し、そのサイズを取得します(最適なソリューションではありません)。

db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})

上記のコードを使用すると、ドキュメントの読み取りはコレクション内のドキュメントのサイズに等しくなり、上記のソリューションの使用を避ける必要があるのはそのためです。

2:-コレクション内のドキュメント数を保存するコレクションを使用して、別のドキュメントを作成します。(最適なソリューション)

db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})

上記では、すべてのカウント情報を保存するために名前がカウントされたドキュメントを作成しました。カウントドキュメントは次の方法で更新できます。

  • ドキュメント数にFirestoreトリガーを作成する
  • 新しいドキュメントが作成されたときに、countsドキュメントのcountプロパティをインクリメントします。
  • ドキュメントが削除されたときにcountsドキュメントのcountプロパティを減らします。

wrt価格(Document Read = 1)と高速データ検索の上記のソリューションは適切です。


3

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

この例ではinstanceCount、ドキュメントがinstancesサブコレクションに追加されるたびに、プロジェクトのフィールドを増分します。フィールドがまだ存在しない場合は作成され、1に増分されます。

インクリメントは内部的にトランザクションですが、1秒よりも頻繁にインクリメントする必要がある場合は、分散カウンターを使用する必要があります。

これは、実装することはしばしば望ましいですonCreateonDeleteいうよりonWrite、あなたが呼び出すとonWrite(あなたがあなたのコレクション内のドキュメントを更新する場合)は、不要な関数呼び出しでより多くのお金を費やしていることを意味するアップデートについて。


2

回避策は次のとおりです。

新しいエントリを作成するたびにトランザクション内でインクリメントするFirebaseドキュメントにカウンタを記述します

新しいエントリのフィールドにカウントを保存します(例:位置:4)。

次に、そのフィールド(DESCの位置​​)にインデックスを作成します。

クエリでスキップ+制限を行うことができます。Where( "position"、 "<" x).OrderBy( "position"、DESC)

お役に立てれば!


1

これらすべてのアイデアを使用して、すべてのカウンター状況(クエリを除く)を処理するユニバーサル関数を作成しました。

唯一の例外は、1秒間に非常に多くの書き込みを行う場合で、速度が低下します。例は次のようになり同類のトレンドポストに。たとえば、ブログ投稿ではやり過ぎであり、コストが高くなります。その場合は、シャードを使用して別の関数を作成することをお勧めします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ジョブ(スケジュールされた関数)を作成して古いイベントを削除し、データベースストレージのコストを節約することもできます。少なくともブレイズプランが必要であり、さらにいくつかの構成がある可能性があります。たとえば、毎週日曜日の午後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が自動的に更新されます。この方法で、他の1つを多くのカウントに簡単に追加できます。これは、物事を自動化する方法のアイデアを与えるだけです。イベントを削除する別の方法も紹介しました。それを削除するには、各日付を読み取る必要があります。そのため、後で削除するために実際に保存されるわけではなく、関数が遅くなるだけです。

/**
 * 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データベースと同様に、コードとデータを繰り返すことでコストを節約できます。


簡単にアクセスできるように、それについての記事を媒体に書いてください。
ahmadalibaloch


0

上記の回答のいくつかに基づいてこれを機能させるのにしばらく時間がかかったので、他の人が使用できるように共有したいと思いました。お役に立てれば幸いです。

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

0

私はさまざまなアプローチでいろいろと試しました。そして最後に、私は方法の1つを改善します。最初に、別のコレクションを作成し、そこにすべてのイベントを保存する必要があります。次に、時間によってトリガーされる新しいラムダを作成する必要があります。このラムダは、イベントコレクション内のイベントをカウントし、イベントドキュメントをクリアします。記事のコードの詳細。 https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca


回答自体に関連する詳細とコードを含めてください。ブログの投稿に人を向けることは、実際にはStackOverflowのポイントではありません。
DBS

0

このクエリにより、ドキュメントの数がカウントされます。

this.db.collection(doc).get().subscribe((data) => {
      count = data.docs.length;
    });

console.log(count)

1
毎回コレクションからすべてのドキュメントをフェッチするので、良い解決策ではありません。それは多くの費用がかかります。より良い方法は、このコレクションに新しいドキュメントが追加されるたびにカウンターを設定して、数千ではなく1つのドキュメントをフェッチすることです。
Corentin Houdayer

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

            int Counter = documentSnapshots.size();

        }
    });

1
この回答では、コード例に関してより多くのコンテキストを使用できます。
ShellNinja
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.