Можно ли подсчитать, сколько элементов в коллекции, используя новую базу данных Firebase Cloud Firestore?
Если да, то как мне это сделать?
firebase
google-cloud-firestore
Гильерме Торрес Кастро
источник
источник
Ответы:
Как и на многие вопросы, ответ - это зависит от обстоятельств .
Вы должны быть очень осторожны при работе с большими объемами данных во внешнем интерфейсе. Помимо того, что ваш интерфейс будет вялым, Firestore также взимает с вас 0,60 доллара за миллион прочитанных вами операций.
Небольшая коллекция (менее 100 документов)
Используйте с осторожностью - пользовательский интерфейс Frontend может пострадать
Обработка этого на внешнем интерфейсе должна быть прекрасной, если вы не используете слишком много логики с этим возвращаемым массивом.
Средняя коллекция (от 100 до 1000 документов)
Используйте с осторожностью - вызовы чтения из Firestore могут дорого стоить
Обработка этого во внешнем интерфейсе неосуществима, так как это имеет слишком большой потенциал, чтобы замедлить работу пользовательской системы. Мы должны обрабатывать эту логику на стороне сервера и возвращать только размер.
Недостатком этого метода является то, что вы все еще вызываете чтение из хранилища (равное размеру вашей коллекции), что в конечном итоге может обойтись вам дороже, чем ожидалось.
Облачная функция:
Внешний интерфейс:
Большая коллекция (1000+ документов)
Самое масштабируемое решение
По состоянию на апрель 2019 года Firestore теперь позволяет увеличивать счетчики полностью атомарно и без предварительного чтения данных . Это гарантирует, что у нас есть правильные значения счетчиков даже при одновременном обновлении из нескольких источников (ранее решалось с использованием транзакций), а также сокращается количество выполняемых нами операций чтения базы данных.
Прослушивая любое удаление или создание документа, мы можем добавить или удалить поле счетчика, которое находится в базе данных.
См. Документацию по firestore - Распределенные счетчики или посмотрите « Агрегирование данных » Джеффа Делани. Его руководства поистине фантастичны для всех, кто использует AngularFire, но его уроки следует перенести и на другие фреймворки.
Облачная функция:
Теперь на веб-интерфейсе вы можете просто запросить это поле numberOfDocs, чтобы получить размер коллекции.
источник
firestore.runTransaction { ... }
блок. Это устраняет проблемы параллелизма при доступеnumberOfDocs
.Самый простой способ сделать это - прочитать размер «querySnapshot».
Вы также можете прочитать длину массива документов внутри querySnapshot.
Или, если «querySnapshot» пуст, путем чтения пустого значения, которое вернет логическое значение.
источник
db.collection.count()
. Думая бросить их только для этогоНасколько я знаю, для этого нет встроенного решения, и прямо сейчас это возможно только в узле sdk. Если у тебя есть
ты можешь использовать
чтобы определить, какое поле вы хотите выбрать. Если вы выполните пустой select (), вы просто получите массив ссылок на документы.
пример:
db.collection('someCollection').select().get().then( (snapshot) => console.log(snapshot.docs.length) );
Это решение является оптимизацией только для худшего случая загрузки всех документов и не масштабируется для больших коллекций!
Также взгляните на это:
Как получить количество документов в коллекции с помощью Cloud Firestore
источник
select(['_id'])
быстрее, чемselect()
Будьте осторожны при подсчете количества документов для больших коллекций . Это немного сложно с базой данных firestore, если вы хотите иметь заранее рассчитанный счетчик для каждой коллекции.
Такой код в этом случае не работает:
Причина в том, что каждый триггер облачного хранилища должен быть идемпотентным, как сказано в документации хранилища хранилищ: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees
Решение
Итак, чтобы предотвратить многократное выполнение вашего кода, вам необходимо управлять событиями и транзакциями. Это мой особый способ работы со счетчиками больших коллекций:
Примеры использования здесь:
Как видите, ключом к предотвращению множественного выполнения является свойство с именем eventId в объекте контекста. Если функция обрабатывалась много раз для одного и того же события, идентификатор события будет одинаковым во всех случаях. К сожалению, в вашей базе данных должна быть коллекция «событий».
источник
context.eventId
при многократных вызовах одного и того же триггера всегда будет одинаковым? В моем тестировании он кажется последовательным, но я не могу найти никакой «официальной» документации, подтверждающей это.В 2020 году этого все еще нет в Firebase SDK, однако он доступен в Firebase Extensions (бета), однако его довольно сложно настроить и использовать ...
Разумный подход
Помощники ... (создание / удаление кажется излишним, но дешевле, чем onUpdate)
export const onCreateCounter = () => async ( change, context ) => { const collectionPath = change.ref.parent.path; const statsDoc = db.doc("counters/" + collectionPath); const countDoc = {}; countDoc["count"] = admin.firestore.FieldValue.increment(1); await statsDoc.set(countDoc, { merge: true }); }; export const onDeleteCounter = () => async ( change, context ) => { const collectionPath = change.ref.parent.path; const statsDoc = db.doc("counters/" + collectionPath); const countDoc = {}; countDoc["count"] = admin.firestore.FieldValue.increment(-1); await statsDoc.set(countDoc, { merge: true }); }; export interface CounterPath { watch: string; name: string; }
Экспортированные крючки Firestore
export const Counters: CounterPath[] = [ { name: "count_buildings", watch: "buildings/{id2}" }, { name: "count_buildings_subcollections", watch: "buildings/{id2}/{id3}/{id4}" } ]; Counters.forEach(item => { exports[item.name + '_create'] = functions.firestore .document(item.watch) .onCreate(onCreateCounter()); exports[item.name + '_delete'] = functions.firestore .document(item.watch) .onDelete(onDeleteCounter()); });
В бою
Строительный кореньБудет отслеживаться коллекция и все вложенные коллекции .
Здесь под
/counters/
корневым путемТеперь количество коллекций будет обновляться автоматически и со временем! Если вам нужен счетчик, просто используйте путь к коллекции с префиксом
counters
.const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts'; const collectionCount = await db .doc('counters/' + collectionPath) .get() .then(snap => snap.get('count'));
Ограничения
Поскольку этот подход использует единую базу данных и документ, он ограничен ограничением Firestore, равным 1 обновлению в секунду для каждого счетчика. В конечном итоге он будет согласованным, но в случаях, когда добавляются / удаляются большие объемы документов, счетчик будет отставать от фактического количества собранных документов.
источник
Я согласен с @Matthew, если вы выполните такой запрос , это будет дорого стоить .
[СОВЕТЫ РАЗРАБОТЧИКАМ ПЕРЕД НАЧАЛОМ ИХ ПРОЕКТОВ]
Поскольку мы предусмотрели эту ситуацию в начале, мы можем фактически создать коллекцию, а именно счетчики с документом, чтобы хранить все счетчики в поле с типом
number
.Например:
Для каждой операции CRUD в коллекции обновите встречный документ:
В следующий раз, когда вы захотите получить номер коллекции, вам просто нужно запросить / указать на поле документа. [1 операция чтения]
Кроме того, вы можете сохранить имя коллекций в массиве, но это будет сложно, состояние массива в firebase показано ниже:
Итак, если вы не собираетесь удалять коллекцию, вы можете использовать массив для хранения списка имен коллекций вместо того, чтобы каждый раз запрашивать всю коллекцию.
Надеюсь, это поможет!
источник
Нет, в настоящее время нет встроенной поддержки запросов агрегирования. Однако вы можете сделать несколько вещей.
Первый документирован здесь . Вы можете использовать транзакции или облачные функции для хранения совокупной информации:
В этом примере показано, как использовать функцию для отслеживания количества оценок в подколлекции, а также средней оценки.
Решение, упомянутое jbb, также полезно, если вы хотите только нечасто подсчитывать документы. Обязательно используйте
select()
оператор, чтобы не загружать все документы (это большая пропускная способность, когда вам нужно только подсчет).select()
пока доступен только в серверных SDK, поэтому это решение не будет работать в мобильном приложении.источник
Увеличьте счетчик с помощью admin.firestore.FieldValue.increment :
В этом примере мы увеличиваем
instanceCount
поле в проекте каждый раз, когда документ добавляется воinstances
вложенную коллекцию. Если поле еще не существует, оно будет создано и увеличено до 1.Увеличение является транзакционным внутри, но вы должны использовать распределенный счетчик, если вам нужно увеличивать чаще, чем каждую 1 секунду.
Это часто предпочтительнее осуществлять
onCreate
иonDelete
вместо того ,onWrite
как вы будете называтьonWrite
обновления означает , что вы тратите больше денег на ненужные функции вызовов (если вы обновляете документы в коллекции).источник
Прямого выбора нет. Вы не можете этого сделать
db.collection("CollectionName").count()
. Ниже приведены два способа узнать количество документов в коллекции.1: - Получить все документы в коллекции, а затем определить ее размер (не лучшее решение)
Используя приведенный выше код, количество прочитанных вами документов будет равно размеру документов в коллекции, и это причина, по которой следует избегать использования вышеуказанного решения.
2: - Создайте отдельный документ в своей коллекции, в котором будет храниться количество документов в коллекции (Лучшее решение)
Выше мы создали документ со счетчиками имен для хранения всей информации о счетчиках. Вы можете обновить документ счетчиков следующим образом: -
по цене (Document Read = 1) и быстрому извлечению данных - это хорошее решение.
источник
Обходной путь:
напишите счетчик в документе firebase, который вы увеличиваете в транзакции каждый раз, когда создаете новую запись
Вы сохраняете счетчик в поле вашей новой записи (например: позиция: 4).
Затем вы создаете индекс для этого поля (позиция DESC).
Вы можете пропустить + ограничение с помощью запроса. Где ("позиция", "<" x) .OrderBy ("позиция", DESC)
Надеюсь это поможет!
источник
Я много пробовал с разными подходами. И наконец, улучшаю один из методов. Для начала нужно создать отдельную коллекцию и сохранить в ней все события. Во-вторых, вам нужно создать новую лямбду, которая будет срабатывать по времени. Эта лямбда будет подсчитывать события в коллекции событий и очищать документы событий. Подробности кода в статье. https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca
источник
Я создал универсальную функцию, используя все эти идеи для обработки всех встречных ситуаций (кроме запросов).
// trigger collections exports.myFunction = functions.firestore .document('{colId}/{docId}') .onWrite(async (change: any, context: any) => { return runCounter(change, context); }); // trigger sub-collections exports.mySubFunction = functions.firestore .document('{colId}/{docId}/{subColId}/{subDocId}') .onWrite(async (change: any, context: any) => { return runCounter(change, context); }); // add change the count const runCounter = async function (change: any, context: any) { const col = context.params.colId; const eventsDoc = '_events'; const countersDoc = '_counters'; // ignore helper collections if (col.startsWith('_')) { return null; } // simplify event types const createDoc = change.after.exists && !change.before.exists; const updateDoc = change.before.exists && change.after.exists; if (updateDoc) { return null; } // check for sub collection const isSubCol = context.params.subDocId; const parentDoc = `${countersDoc}/${context.params.colId}`; const countDoc = isSubCol ? `${parentDoc}/${context.params.docId}/${context.params.subColId}` : `${parentDoc}`; // collection references const countRef = db.doc(countDoc); const countSnap = await countRef.get(); // increment size if doc exists if (countSnap.exists) { // createDoc or deleteDoc const n = createDoc ? 1 : -1; const i = admin.firestore.FieldValue.increment(n); // create event for accurate increment const eventRef = db.doc(`${eventsDoc}/${context.eventId}`); return db.runTransaction(async (t: any): Promise<any> => { const eventSnap = await t.get(eventRef); // do nothing if event exists if (eventSnap.exists) { return null; } // add event and update size await t.update(countRef, { count: i }); return t.set(eventRef, { completed: admin.firestore.FieldValue.serverTimestamp() }); }).catch((e: any) => { console.log(e); }); // otherwise count all docs in the collection and add size } else { const colRef = db.collection(change.after.ref.parent.path); return db.runTransaction(async (t: any): Promise<any> => { // update size const colSnap = await t.get(colRef); return t.set(countRef, { count: colSnap.size }); }).catch((e: any) => { console.log(e); });; } }
Это обрабатывает события, приращения и транзакции. Прелесть в этом заключается в том, что если вы не уверены в точности документа (возможно, еще в бета-версии), вы можете удалить счетчик, чтобы он автоматически складывал их при следующем триггере. Да, это стоит денег, иначе не удаляйте его.
То же самое для подсчета:
const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts'; const colSnap = await db.doc('_counters/' + collectionPath).get(); const count = colSnap.get('count');
Кроме того, вы можете создать задание cron (запланированная функция) для удаления старых событий, чтобы сэкономить деньги на хранилище базы данных. Вам нужен хотя бы план пламени, и может быть еще какая-то конфигурация. Например, вы можете запускать его каждое воскресенье в 23:00. https://firebase.google.com/docs/functions/schedule-functions
Это не проверено , но должно работать с несколькими настройками:
exports.scheduledFunctionCrontab = functions.pubsub.schedule('5 11 * * *') .timeZone('America/New_York') .onRun(async (context) => { // get yesterday const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); const eventFilter = db.collection('_events').where('completed', '<=', yesterday); const eventFilterSnap = await eventFilter.get(); eventFilterSnap.forEach(async (doc: any) => { await doc.ref.delete(); }); return null; });
И наконец, не забудьте защитить свои коллекции в firestore.rules :
match /_counters/{document} { allow read; allow write: if false; } match /_events/{document} { allow read, write: if false; }
Обновление: запросы
Добавляя к моему другому ответу, если вы также хотите автоматизировать подсчет запросов, вы можете использовать этот измененный код в своей облачной функции:
if (col === 'posts') { // counter reference - user doc ref const userRef = after ? after.userDoc : before.userDoc; // query reference const postsQuery = db.collection('posts').where('userDoc', "==", userRef); // add the count - postsCount on userDoc await addCount(change, context, postsQuery, userRef, 'postsCount'); } return delEvents();
Что автоматически обновит postsCount в userDocument. Таким образом вы можете легко добавить еще один ко многим счетчикам. Это просто дает вам представление о том, как вы можете автоматизировать вещи. Я также дал вам другой способ удалить события. Вы должны прочитать каждую дату, чтобы удалить ее, так что это не спасет вас от удаления их позже, просто замедлит работу.
/** * Adds a counter to a doc * @param change - change ref * @param context - context ref * @param queryRef - the query ref to count * @param countRef - the counter document ref * @param countName - the name of the counter on the counter document */ const addCount = async function (change: any, context: any, queryRef: any, countRef: any, countName: string) { // events collection const eventsDoc = '_events'; // simplify event type const createDoc = change.after.exists && !change.before.exists; // doc references const countSnap = await countRef.get(); // increment size if field exists if (countSnap.get(countName)) { // createDoc or deleteDoc const n = createDoc ? 1 : -1; const i = admin.firestore.FieldValue.increment(n); // create event for accurate increment const eventRef = db.doc(`${eventsDoc}/${context.eventId}`); return db.runTransaction(async (t: any): Promise<any> => { const eventSnap = await t.get(eventRef); // do nothing if event exists if (eventSnap.exists) { return null; } // add event and update size await t.set(countRef, { [countName]: i }, { merge: true }); return t.set(eventRef, { completed: admin.firestore.FieldValue.serverTimestamp() }); }).catch((e: any) => { console.log(e); }); // otherwise count all docs in the collection and add size } else { return db.runTransaction(async (t: any): Promise<any> => { // update size const colSnap = await t.get(queryRef); return t.set(countRef, { [countName]: colSnap.size }, { merge: true }); }).catch((e: any) => { console.log(e); });; } } /** * Deletes events over a day old */ const delEvents = async function () { // get yesterday const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); const eventFilter = db.collection('_events').where('completed', '<=', yesterday); const eventFilterSnap = await eventFilter.get(); eventFilterSnap.forEach(async (doc: any) => { await doc.ref.delete(); }); return null; }
источник
Решение с использованием разбивки на страницы с помощью
offset
&limit
:findDocsPage
это рекурсивный метод поиска всех страниц коллекцииselectedFields
для оптимизации запроса и получения только поля идентификатора вместо всего тела документаlimit
максимальный размер каждой страницы запросаpage
определить начальную страницу для разбивки на страницыисточник
Мне потребовалось время, чтобы заставить это работать на основе некоторых из приведенных выше ответов, поэтому я подумал, что поделюсь им для использования другими. Надеюсь, это будет полезно.
источник
Это использует подсчет для создания числового уникального идентификатора. В моем использовании я никогда не буду уменьшать значение , даже если
document
идентификатор, для которого требуется идентификатор, будет удален.При
collection
создании, которому требуется уникальное числовое значениеappData
одним документомset
с.doc
идентификаторомonly
uniqueNumericIDAmount
0 вfirebase firestore console
doc.data().uniqueNumericIDAmount + 1
как уникальный числовой идентификаторappData
коллекциюuniqueNumericIDAmount
с помощьюfirebase.firestore.FieldValue.increment(1)
источник
тогда, если вы хотите использовать его в переменной String, тогда
источник
источник