Как получить количество документов в коллекции с Cloud Firestore
В Firestore, как я могу получить общее количество документов в коллекции?
Например, если у меня есть
/people
/123456
/name - 'John'
/456789
/name - 'Jane'
Я хочу запросить, сколько у меня людей и получить 2.
Я мог бы сделать запрос на /people, а затем получить длину возвращаемых результатов, но это кажется пустой тратой, особенно потому, что я буду делать это на больших наборах данных.
Ответы
Ответ 1
В настоящее время у вас есть 3 варианта:
Вариант 1: Клиентская сторона
Это в основном подход, который вы упомянули. Выбрать все из коллекции и рассчитывать на стороне клиента. Это работает достаточно хорошо для небольших наборов данных, но, очевидно, не работает, если набор данных больше.
Вариант 2: лучшее время записи
При таком подходе вы можете использовать облачные функции для обновления счетчика для каждого добавления и удаления из коллекции.
Это хорошо работает для любого размера набора данных, если добавления/удаления происходят только со скоростью, меньшей или равной 1 в секунду. Это дает вам один документ для чтения, чтобы сразу подсчитать практически текущий счет.
Если вам нужно превышать 1 раз в секунду, вам необходимо реализовать распределенные счетчики в соответствии с нашей документацией.
Вариант 3: точное время записи
Вместо использования облачных функций в вашем клиенте вы можете обновить счетчик одновременно с добавлением или удалением документа. Это означает, что счетчик также будет текущим, но вам нужно обязательно включать эту логику в любое место, куда вы добавляете или удаляете документы.
Как и в варианте 2, вам нужно будет реализовать распределенные счетчики, если вы хотите увеличить в секунду
Ответ 2
Если вы используете AngulareFire2, вы можете сделать это (при условии, что в вашем конструкторе private afs: AngularFirestore
):
this.afs.collection(myCollection).valueChanges().subscribe( values => console.log(values.length));
Здесь values
- это массив всех элементов в myCollection
. Вам не нужны метаданные, поэтому вы можете valueChanges()
использовать valueChanges()
.
Ответ 3
Агрегации - это путь (функции firebase выглядят как рекомендуемый способ обновления этих агрегаций, поскольку клиентская сторона предоставляет информацию пользователю, которого вы не хотите раскрывать) https://firebase.google.com/docs/firestore/solutions/aggregation
Другой способ (НЕ рекомендуется), который не подходит для больших списков и включает загрузку всего списка: res.size, как в этом примере:
db.collection('products').get().then(res => console.log(res.size))
Ответ 4
Будьте внимательны, считая количество документов для больших коллекций с облачной функцией. Это немного сложно с базой данных 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;
});
Причина в том, что каждый триггер облачного пожарного хранилища должен быть идемпотентным, как сказано в документации пожарного магазина: 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 в объекте контекста. Если функция была обработана много раз для одного и того же события, идентификатор события будет одинаковым во всех случаях. К сожалению, у вас должна быть коллекция "events" в вашей базе данных.
Ответ 5
После ответа Dan. Вы можете иметь отдельный счетчик в своей базе данных и использовать 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