Количество накоплений Cloud Firestore
Можно ли подсчитать, сколько элементов в коллекции есть, используя новую базу данных Firebase, Cloud Firestore?
Если так, то как мне это сделать?
Ответы
Ответ 1
Обновление (апрель 2019 г.) - FieldValue.increment (см. Решение для больших коллекций)
Как и во многих вопросах, ответ - это зависит.
Вы должны быть очень осторожны при обработке больших объемов данных на внешнем интерфейсе. Помимо того, что ваш интерфейс чувствует себя вялым, Firestore также берет с вас $ 0,60 за миллион считываний, которые вы делаете.
Небольшая коллекция (менее 100 документов)
Используйте с осторожностью - пользовательский интерфейс Frontend может получить удар
Обработка этого во внешнем интерфейсе должна быть в порядке, если вы не слишком много логику с этим возвращенным массивом
db.collection('...').get().then(snap => {
size = snap.size // will return the collection size
});
Средний сборник (от 100 до 1000 документов)
Используйте с осторожностью - вызовы чтения 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 года Firestore теперь позволяет увеличивать счетчики, полностью атомарно, без предварительного чтения данных. Это обеспечивает правильные значения счетчиков даже при одновременном обновлении из нескольких источников (ранее решалось с помощью транзакций), а также уменьшает количество операций чтения базы данных, которые мы выполняем.
Прослушивая удаление или создание любого документа, мы можем добавить или удалить из поля подсчета, которое находится в базе данных.
См. Документацию пожарного магазина - Распределенные счетчики. Или посмотрите на Агрегацию данных Джеффа Делани. Его руководства действительно фантастические для любого, кто использует 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, чтобы получить размер коллекции.
Ответ 2
Простейший способ сделать это - прочитать размер "querySnapshot".
db.collection("cities").get().then(function(querySnapshot) {
console.log(querySnapshot.size);
});
Вы также можете прочитать длину массива docs внутри "querySnapshot".
querySnapshot.docs.length;
Или, если "querySnapshot" пуст, читая пустое значение, которое вернет логическое значение.
querySnapshot.empty;
Ответ 3
Насколько я знаю, для этого нет встроенного решения, и это возможно только в узле sdk прямо сейчас. Если у тебя есть
db.collection( 'someCollection')
ты можешь использовать
.select([поля])
чтобы определить, какое поле вы хотите выбрать. Если вы выполните пустой select(), вы получите массив ссылок на документы.
пример:
db.collection('someCollection').select().get().then( (snapshot) => console.log(snapshot.docs.length) );
Это решение является лишь оптимизацией для наихудшего случая загрузки всех документов и не масштабируется на больших коллекциях!
Также взгляните на это:
Как получить количество документов в коллекции с Cloud Firestore
Ответ 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
Я согласен с @Matthew, это будет дорого стоить, если вы выполните такой запрос.
[КОНСУЛЬТАЦИЯ ДЛЯ РАЗРАБОТЧИКОВ ПЕРЕД НАЧАЛОМ ИХ ПРОЕКТОВ]
Поскольку мы предвидели эту ситуацию в начале, мы можем сделать коллекцию, а именно счетчики с документом для хранения всех счетчиков в поле с number
типа.
Например:
Для каждой операции CRUD в коллекции обновите встречный документ:
- Когда вы создаете новую коллекцию/подмножество: (+1 в счетчике) [1 операция записи]
- Когда вы удаляете коллекцию/подмножество: (-1 в счетчике) [1 операция записи]
- Когда вы обновляете существующую коллекцию/субколлекцию, ничего не делайте в счетчике: (0)
- Когда вы читаете существующую коллекцию/субколлекцию, ничего не делайте в счетчике: (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'}
Итак, если вы не собираетесь удалять коллекцию, вы можете фактически использовать массив для хранения списка имен коллекций, а не для запроса всей коллекции каждый раз.
Надеюсь, поможет!
Ответ 6
Нет, нет встроенной поддержки запросов агрегирования прямо сейчас. Однако есть несколько вещей, которые вы могли бы сделать.
Первое описано здесь. Вы можете использовать транзакции или облачные функции для поддержания общей информации:
В этом примере показано, как использовать функцию для отслеживания количества оценок в субколлекции, а также среднего рейтинга.
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 сервера, так что решение не будет работать в мобильном приложении.
Ответ 7
Увеличить счетчик с помощью 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
для обновлений, что означает, что вы тратите больше денег на ненужные вызовы функций (если вы обновляете документы в своей коллекции).
Ответ 8
Вы можете использовать функции cloudbase для отслеживания количества документов в коллекции. Подробнее см. на странице https://firebase.google.com/docs/functions.
Ответ 9
Почему все перерабатывают решение?
Это просто - при использовании последней версии Firebase 7.0.0 должен работать следующий пример кода javascript для сервера:
var collectionSize = await admin.firestore()
.collection(collectionName)
.listDocuments()
.then(val => {
return val.length
})
Это работает как 1 запрос на чтение, потому что все, что он делает, это захватывает список идентификаторов в коллекции один раз, а затем возвращает длину списка.
Согласно их ценовой документации,
(Для) запроса списка идентификаторов коллекции вам выставляется счет за один прочитанный документ. Если для получения полного набора результатов требуется более одного запроса (например, если вы используете нумерацию страниц), вам выставляется счет один раз за запрос.
Ответ 10
Прямых опций нет. Вы не можете сделать db.collection("CollectionName").count()
. Ниже приведены два способа, с помощью которых вы можете найти количество документов в коллекции.
1: - Получить все документы в коллекции, а затем получить ее размер. (Не лучшее решение)
db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})
Используя приведенный выше код, ваш документ будет читаться равным размеру документов в коллекции, и именно поэтому следует избегать использования вышеуказанного решения.
2: - Создайте отдельный документ в вашей коллекции, в котором будет храниться количество документов в коллекции. (Лучшее решение)
db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})
Выше мы создали документ с подсчетом имен для хранения всей информации о подсчете. Вы можете обновить документ подсчета следующим образом: -
- Создать пожарные триггеры на счетчиках документов
- Увеличивать свойство count документа count при создании нового документа.
- Уменьшите свойство count документа count при удалении документа.
по цене (Document Read = 1) и быстрому поиску данных вышеупомянутое решение хорошо.
Ответ 11
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
@Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
int Counter = documentSnapshots.size();
}
});
Ответ 12
Мне потребовалось некоторое время, чтобы заставить это работать, основываясь на некоторых из приведенных выше ответов, поэтому я решил поделиться им с другими. Я надеюсь, что это полезно.
'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;
});