コレクションが新しいfirebaseデータベースfirestoreを使用しているアイテムの数を数えることは可能ですか?
もしそうなら、どのように私はそれをするのですか?
多くの質問と同様に、答えは - です。それはに依存します。
フロントエンドで大量のデータを処理するときは、細心の注意を払う必要があります。フロントエンドの動作が遅くなることに加えて、 Firestoreも 100万リードあたり$ 0.60を請求します 。
慎重に使用する - フロントエンドのユーザーエクスペリエンスが影響を受ける可能性があります
この返された配列であまりにも多くのロジックを実行していない限り、フロントエンドでこれを処理しても問題ありません。
db.collection('...').get().then(snap => {
size = snap.size // will return the collection size
});
注意して使用してください - 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
})
最もスケーラブルなソリューション
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フィールドを照会することができます。
そのための最も簡単な方法は、 "querySnapshot"のサイズを読むことです。
db.collection("cities").get().then(function(querySnapshot) {
console.log(querySnapshot.size);
});
"querySnapshot"の中のdocs配列の長さを読むこともできます。
querySnapshot.docs.length;
または、空の値を読み込んで "querySnapshot"が空の場合、ブール値が返されます。
querySnapshot.empty;
私が知っている限りでは、これに対する組み込みの解決策はなく、それは今のところノードsdkでのみ可能です。あなたが持っているなら
db.collection( 'someCollection')
あなたが使用することができます
.select([fields])
どのフィールドを選択するかを定義します。空のselect()を実行すると、ドキュメント参照の配列を取得するだけです。
例:
db.collection('someCollection').select().get().then( (snapshot) => console.log(snapshot.docs.length) );
この解決策は、すべてのドキュメントをダウンロードするという最悪の場合の最適化にすぎず、大規模コレクションでは拡張できません。
大規模なコレクションのドキュメント数を慎重にカウントしてください。すべてのコレクションに対して事前に計算されたカウンターが必要な場合、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はすべての場合で同じになります。残念ながら、データベースには「イベント」コレクションが必要です。
いいえ、今のところ集約クエリの組み込みサポートはありません。しかし、できることがいくつかあります。
最初のものは ここに文書化されています 。トランザクションまたはクラウド機能を使用して集約情報を管理できます。
この例では、サブコレクション内の評価数と平均評価を追跡するための関数の使用方法を示します。
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でのみ利用可能であるため、ソリューションはモバイルアプリでは機能しません。
私は@Matthewに同意します、あなたがそのような質問を実行するならば、それはたくさんの費用がかかります。
[プロジェクトを始める前の開発者へのアドバイス]
最初はこの状況を予見していたので、実際にコレクションを作成することができます。つまり、ドキュメント付きのカウンタで、すべてのカウンタをnumber
型のフィールドに格納することができます。
例えば:
コレクションに対するCRUD操作ごとに、カウンター文書を更新します。
次回、コレクションの数を取得したいときは、ドキュメントフィールドをクエリ/ポイントするだけです。 [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を使用してコレクション名のリストを保管することができます。
それが役に立てば幸い!
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
を実装することをお勧めします。これは、不要な関数の呼び出しにより多くのお金を費やすことを意味します(コレクションのドキュメントを更新する場合)。
直接利用可能なオプションはありません。あなたはdb.collection("CollectionName").count()
をすることができません。以下は、コレクション内の文書数のカウントを見つけるための2つの方法です。
db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})
上記のコードを使用することで、あなたの文書の読み取りはコレクション内の文書のサイズに等しくなり、それが上記の解決策の使用を避けなければならない理由です。
db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})
上記では、すべてのカウント情報を格納するために名前カウントを持つドキュメントを作成しました。次のようにしてカウントドキュメントを更新できます。
w.r.t価格(Document Read = 1)と高速データ検索上記の解決策は良いです。
上記の回答のいくつかに基づいてこれを機能させるためにしばらく時間がかかったので、他の人が使用するために共有すると思いました。役に立つことを願っています。
'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;
});