web-dev-qa-db-ja.com

Cloud Firestoreを使用してコレクション内のドキュメント数のカウントを取得する方法

Firestoreで、コレクション内のドキュメントの総数を取得するにはどうすればよいですか?

たとえば、私が持っている場合

/people
    /123456
        /name - 'John'
    /456789
        /name - 'Jane'

私が持っている人の数を問い合わせて、2を得ます。

/ peopleでクエリを実行し、返された結果の長さを取得することもできますが、特に大きなデータセットでこれを実行するため、それは無駄に思えます。

36
justinbc820

現在、次の3つのオプションがあります。

オプション1:クライアント側

これは基本的にあなたが言及したアプローチです。コレクションからすべてを選択し、クライアント側でカウントします。これは小さなデータセットでは十分に機能しますが、データセットが大きい場合は明らかに機能しません。

オプション2:書き込み時のベストエフォート

このアプローチでは、Cloud Functionsを使用して、コレクションに追加および削除するたびにカウンターを更新できます。

これは、追加/削除が1秒あたり1以下のレートでのみ発生する限り、どのデータセットサイズでもうまく機能します。これにより、すぐにalmost currentカウントを取得するために、1つのドキュメントを読むことができます。

毎秒1を超える必要がある場合は、 ドキュメントごとの分散カウンター を実装する必要があります。

オプション3:正確な書き込み時間

Cloud Functionsを使用する代わりに、クライアントでドキュメントを追加または削除すると同時にカウンターを更新できます。これは、カウンタも最新であることを意味しますが、ドキュメントを追加または削除する場所には必ずこのロジックを含める必要があります。

オプション2と同様に、毎秒を超えたい場合は、分散カウンターを実装する必要があります。

32
Dan McGrath

AngulareFire2を使用している場合は、次のことができます(private afs: AngularFirestoreがコンストラクターに挿入されていると仮定します):

this.afs.collection(myCollection).valueChanges().subscribe( values => console.log(values.length));

ここで、valuesmyCollection内のすべてのアイテムの配列です。メタデータは必要ないので、valueChanges()メソッドを直接使用できます。

4
Johan Chouquet

集約は行く方法です(クライアント側が公開したくないかもしれないユーザーに情報を公開するので、firebase関数はこれらの集約を更新する推奨方法のように見えます) https://firebase.google.com/docs/firestore/ソリューション/集計

別の方法(推奨されません)は、大きなリストには適さず、リスト全体をダウンロードする必要があります:この例のようなres.size:

db.collection('products').get().then(res => console.log(res.size))
3
Ben Cochrane

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

1
Ferran Verdés

次のダンの回答:データベース内に個別のカウンターを作成し、Cloud Functionsを使用してメンテナンスできます。 (書き込み時のベストエフォート

// Example of performing an increment when item is added
module.exports.incrementIncomesCounter = collectionRef.onCreate(event => {
  const counterRef = event.data.ref.firestore.doc('counters/incomes')

  counterRef.get()
  .then(documentSnapshot => {
    const currentCount = documentSnapshot.exists ? documentSnapshot.data().count : 0

    counterRef.set({
      count: Number(currentCount) + 1
    })
    .then(() => {
      console.log('counter has increased!')
    })
  })
})

このコードは、それを行う方法の完全な例を示しています: https://Gist.github.com/saintplay/3f965e0aea933a1129cc2c9a823e74d7

0
Saint Play