web-dev-qa-db-ja.com

Firebase Firestoreコレクション数

コレクションが新しいfirebaseデータベースfirestoreを使用しているアイテムの数を数えることは可能ですか?

もしそうなら、どのように私はそれをするのですか?


更新(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のread呼び出しには多大な費用がかかる可能性があります

フロントエンドでこれを処理することはユーザーシステムを遅くするには余りにも多くの可能性があるので実行可能ではありません。このロジックサーバー側を処理してサイズのみを返すようにします。

この方法の欠点は、(コレクションのサイズに等しい)firestore readをまだ呼び出していることです。結局、これは予想以上にコストがかかる可能性があります。

クラウド機能:

...
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フィールドを照会することができます。

97
Matthew Mullin

そのための最も簡単な方法は、 "querySnapshot"のサイズを読むことです。

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

"querySnapshot"の中のdocs配列の長さを読むこともできます。

querySnapshot.docs.length;

または、空の値を読み込んで "querySnapshot"が空の場合、ブール値が返されます。

querySnapshot.empty;
17
Ompel

私が知っている限りでは、これに対する組み込みの解決策はなく、それは今のところノードsdkでのみ可能です。あなたが持っているなら

db.collection( 'someCollection')

あなたが使用することができます

.select([fields])

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

例:

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

この解決策は、すべてのドキュメントをダウンロードするという最悪の場合の最適化にすぎず、大規模コレクションでは拡張できません。

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

11
jbb

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

7
Ferran Verdés

いいえ、今のところ集約クエリの組み込みサポートはありません。しかし、できることがいくつかあります。

最初のものは ここに文書化されています 。トランザクションまたはクラウド機能を使用して集約情報を管理できます。

この例では、サブコレクション内の評価数と平均評価を追跡するための関数の使用方法を示します。

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でのみ利用可能であるため、ソリューションはモバイルアプリでは機能しません。

4
Sam Stern

私は@Matthewに同意します、あなたがそのような質問を実行するならば、それはたくさんの費用がかかります

[プロジェクトを始める前の開発者へのアドバイス]

最初はこの状況を予見していたので、実際にコレクションを作成することができます。つまり、ドキュメント付きのカウンタで、すべてのカウンタをnumber型のフィールドに格納することができます。

例えば:

コレクションに対するCRUD操作ごとに、カウンター文書を更新します。

  1. create新しいコレクション/サブコレクションを作成する場合:(カウンターの+ 1)[1書き込み操作]
  2. deleteコレクション/サブコレクション:(カウンターの - 1)[1書き込み操作]
  3. update既存のコレクション/サブコレクションの場合、カウンター文書では何もしないでください:(0)
  4. 既存のコレクション/サブコレクションをreadにした場合、カウンター文書では何もしないでください:(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'}

そのため、コレクションを削除しないのであれば、毎回すべてのコレクションを照会するのではなく、実際にarrayを使用してコレクション名のリストを保管することができます。

それが役に立てば幸い!

4
Angus Tay

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秒ごとより頻繁に増分する必要がある場合は、 分散カウンタ を使用する必要があります。

更新のためにonCreateを呼び出すので、onDeleteよりもonWriteおよびonWriteを実装することをお勧めします。これは、不要な関数の呼び出しにより多くのお金を費やすことを意味します(コレクションのドキュメントを更新する場合)。

2
Dominic

直接利用可能なオプションはありません。あなたは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トリガーを作成する
  • 新しい文書が作成されたら、count文書のcountプロパティを増やします。
  • 文書が削除されたときに、カウント文書のcountプロパティを減らします。

w.r.t価格(Document Read = 1)と高速データ検索上記の解決策は良いです。

0
Nipun Madan

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

'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
Rob Phillips