Количество коллекций Cloud Firestore

Ответы:

214

Как и на многие вопросы, ответ - это зависит от обстоятельств .

Вы должны быть очень осторожны при работе с большими объемами данных во внешнем интерфейсе. Помимо того, что ваш интерфейс будет вялым, 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 теперь позволяет увеличивать счетчики полностью атомарно и без предварительного чтения данных . Это гарантирует, что у нас есть правильные значения счетчиков даже при одновременном обновлении из нескольких источников (ранее решалось с использованием транзакций), а также сокращается количество выполняемых нами операций чтения базы данных.


Прослушивая любое удаление или создание документа, мы можем добавить или удалить поле счетчика, которое находится в базе данных.

См. Документацию по 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, чтобы получить размер коллекции.

Мэтью Маллин
источник
18
Отличное решение для больших коллекций! Я просто хотел бы добавить, что разработчики должны заключать чтение и запись в firestore.runTransaction { ... }блок. Это устраняет проблемы параллелизма при доступе numberOfDocs.
efemoney
2
Эти методы используют пересчет количества записей. Если вы используете счетчик и увеличиваете счетчик с помощью транзакции, разве это не приведет к тому же результату без дополнительных затрат и необходимости облачной функции?
user3836415
12
Решение для больших коллекций не идемпотентно и не работает в любом масштабе. Триггеры документов Firestore гарантированно запускаются хотя бы один раз, но могут запускаться несколько раз. Когда это происходит, даже поддержание обновления внутри транзакции может выполняться более одного раза, что даст вам ложное число. Когда я попробовал это, у меня возникли проблемы с созданием менее десятка документов одновременно.
Tym Pollack
2
Привет, @TymPollack. Я заметил некорректное поведение при использовании облачных триггеров. Есть ли шанс, что вы могли бы связать меня со статьей или форумом, чтобы объяснить свое поведение?
Мэтью Маллин
2
@cmprogram вы читаете всю коллекцию и данные, когда используете db.collection ('...') ... поэтому, когда вам не нужны данные, вы правы - вы можете легко запросить список идентификаторы коллекции (не данные документов коллекции), и это считается одним чтением.
атерешков 04
26

Самый простой способ сделать это - прочитать размер «querySnapshot».

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

Вы также можете прочитать длину массива документов внутри querySnapshot.

querySnapshot.docs.length;

Или, если «querySnapshot» пуст, путем чтения пустого значения, которое вернет логическое значение.

querySnapshot.empty;
Ompel
источник
77
Имейте в виду, что каждый документ «стоит» одного чтения. Итак, если вы таким образом подсчитаете 100 элементов в коллекции, с вас начисляется плата за 100 чтений!
Георг
Верно, но другого способа подсчитать количество документов в коллекции нет. И если вы уже получили коллекцию, чтение массива «docs» больше не потребует выборки и, следовательно, не будет «стоить» дополнительных чтений.
Ompel
5
Это читает все документы в памяти! Удачи с этим для больших наборов данных ...
Дэн Даскалеску
99
Это действительно невероятно, что у Firebase Firestore нет такого db.collection.count(). Думая бросить их только для этого
Blue Bot
9
Особенно для больших коллекций несправедливо взимать с нас плату, как если бы мы действительно загрузили и использовали все документы. Счетчик для стола (коллекции) - такая основная функция. Учитывая их модель ценообразования и тот факт, что Firestore был запущен в 2017 году, просто невероятно, что Google не предоставляет альтернативного способа получить размер коллекции. Пока они этого не сделают, им следует по крайней мере избегать взимания платы за это.
ниббана
25

Насколько я знаю, для этого нет встроенного решения, и прямо сейчас это возможно только в узле sdk. Если у тебя есть

db.collection('someCollection')

ты можешь использовать

.select([fields])

чтобы определить, какое поле вы хотите выбрать. Если вы выполните пустой select (), вы просто получите массив ссылок на документы.

пример:

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

Это решение является оптимизацией только для худшего случая загрузки всех документов и не масштабируется для больших коллекций!

Также взгляните на это:
Как получить количество документов в коллекции с помощью Cloud Firestore

jbb
источник
По моему опыту, select(['_id'])быстрее, чемselect()
JAnton
13

Будьте осторожны при подсчете количества документов для больших коллекций . Это немного сложно с базой данных 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 в объекте контекста. Если функция обрабатывалась много раз для одного и того же события, идентификатор события будет одинаковым во всех случаях. К сожалению, в вашей базе данных должна быть коллекция «событий».

Ферран Вердес
источник
2
Они формулируют это так, как будто это поведение будет исправлено в версии 1.0. Функции Amazon AWS страдают той же проблемой. Такая простая вещь, как подсчет полей, становится сложной и дорогой.
MarcG
Сейчас попробую, так как это кажется лучшим решением. Вы когда-нибудь возвращались и очищали свою коллекцию событий? Я думал просто добавить поле даты и очистить данные старше одного дня или чего-то еще, чтобы набор данных был небольшим (возможно, 1 миллион + событий в день). Если в FS нет простого способа сделать это ... я использовал FS всего несколько месяцев.
Tym Pollack
1
Можем ли мы убедиться, что context.eventIdпри многократных вызовах одного и того же триггера всегда будет одинаковым? В моем тестировании он кажется последовательным, но я не могу найти никакой «официальной» документации, подтверждающей это.
Майк Маклин
2
Итак, после некоторого использования этого я обнаружил, что, хотя это решение работает ровно с одной записью, и это здорово, если слишком много триггеров срабатывает из нескольких документов, записываемых одновременно и пытающихся обновить один и тот же документ счетчика, вы можете получить конфликтные ошибки из firestore. Вы сталкивались с ними и как вам это удалось? (Ошибка: 10 ABORTED: слишком много разногласий по этим документам. Повторите попытку.)
Pollack
1
@TymPollack посмотрите на распределенные счетчики, запись документов ограничена примерно одним обновлением в секунду
Джейми
10

В 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 обновлению в секунду для каждого счетчика. В конечном итоге он будет согласованным, но в случаях, когда добавляются / удаляются большие объемы документов, счетчик будет отставать от фактического количества собранных документов.

Бен Виндинг
источник
Разве это не связано с тем же ограничением «1 обновление документа в секунду»?
Айяппа
Да, но в конечном итоге это согласовано, то есть количество коллекций в конечном итоге выровняется с фактическим количеством коллекций, это самое простое решение для реализации, и во многих случаях приемлемо небольшое отставание в подсчете.
Бен Виндинг,
Ограничения: 10 000 в секунду (согласно официальной документации: firebase.google.com/products/extensions/firestore-counter )
Пуджа
1
@Pooja, что ограничение неверно, так как оно относится к распределенным счетчикам, вышеуказанное решение не распространяется.
Бен Виндинг,
8

Я согласен с @Matthew, если вы выполните такой запрос , это будет дорого стоить .

[СОВЕТЫ РАЗРАБОТЧИКАМ ПЕРЕД НАЧАЛОМ ИХ ПРОЕКТОВ]

Поскольку мы предусмотрели эту ситуацию в начале, мы можем фактически создать коллекцию, а именно счетчики с документом, чтобы хранить все счетчики в поле с типом number.

Например:

Для каждой операции CRUD в коллекции обновите встречный документ:

  1. При создании новой коллекции / субколлекции: (+1 в счетчике) [1 операция записи]
  2. При удалении коллекции / подколлекции: (-1 в счетчике) [1 операция записи]
  3. Когда вы обновляете существующую коллекцию / вложенную коллекцию, ничего не делайте со счетным документом: (0)
  4. Когда вы читаете существующую коллекцию / подколлекцию, ничего не делайте со встречным документом: (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'}

Итак, если вы не собираетесь удалять коллекцию, вы можете использовать массив для хранения списка имен коллекций вместо того, чтобы каждый раз запрашивать всю коллекцию.

Надеюсь, это поможет!

Ангус Тай
источник
Может быть, для небольшой коллекции. Но имейте в виду, что предел размера документа Firestore составляет ~ 1 МБ, что, если идентификаторы документов в коллекции создаются автоматически (20 байт), вы сможете сохранить только ~ 52 425 из них перед документом, содержащим массив. слишком большой. Я предполагаю, что в качестве обходного пути вы могли бы создавать новый документ каждые 50 000 элементов, но тогда поддержание этих массивов было бы совершенно неуправляемым. Кроме того, по мере увеличения размера документа для чтения и обновления потребуется больше времени, что в конечном итоге приведет к тому, что любые другие операции над ним будут считаться конфликтными.
Pollack
5

Нет, в настоящее время нет встроенной поддержки запросов агрегирования. Однако вы можете сделать несколько вещей.

Первый документирован здесь . Вы можете использовать транзакции или облачные функции для хранения совокупной информации:

В этом примере показано, как использовать функцию для отслеживания количества оценок в подколлекции, а также средней оценки.

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, поэтому это решение не будет работать в мобильном приложении.

Сэм Стерн
источник
1
Это решение не идемпотентно, поэтому любые триггеры, срабатывающие более одного раза, сбрасывают ваше количество оценок и среднее значение.
Pollack
4

Увеличьте счетчик с помощью 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обновления означает , что вы тратите больше денег на ненужные функции вызовов (если вы обновляете документы в коллекции).

Доминик
источник
4

Прямого выбора нет. Вы не можете этого сделать 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)
})

Выше мы создали документ со счетчиками имен для хранения всей информации о счетчиках. Вы можете обновить документ счетчиков следующим образом: -

  • Создавайте триггеры firestore по количеству документов
  • Увеличивайте свойство count для документа counts при создании нового документа.
  • Уменьшите свойство count документа counts при удалении документа.

по цене (Document Read = 1) и быстрому извлечению данных - это хорошее решение.

Нипун Мадан
источник
2

Обходной путь:

напишите счетчик в документе firebase, который вы увеличиваете в транзакции каждый раз, когда создаете новую запись

Вы сохраняете счетчик в поле вашей новой записи (например: позиция: 4).

Затем вы создаете индекс для этого поля (позиция DESC).

Вы можете пропустить + ограничение с помощью запроса. Где ("позиция", "<" x) .OrderBy ("позиция", DESC)

Надеюсь это поможет!

Катан Шах
источник
2

Я много пробовал с разными подходами. И наконец, улучшаю один из методов. Для начала нужно создать отдельную коллекцию и сохранить в ней все события. Во-вторых, вам нужно создать новую лямбду, которая будет срабатывать по времени. Эта лямбда будет подсчитывать события в коллекции событий и очищать документы событий. Подробности кода в статье. https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca

Игорь Маланюк
источник
Пожалуйста, включите соответствующие детали и код в сам ответ , указывать людям на ваши сообщения в блоге на самом деле не суть StackOverflow.
DBS
1

Я создал универсальную функцию, используя все эти идеи для обработки всех встречных ситуаций (кроме запросов).

Единственное исключение - когда вы делаете так много записей в секунду, это замедляет вас. Примером могут служить лайки на популярном посте. Например, для публикации в блоге это излишне, и вам обойдется дороже. Я предлагаю в этом случае создать отдельную функцию с использованием шардов: https://firebase.google.com/docs/firestore/solutions/counters

// 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;
}

Я также должен предупредить вас, что универсальные функции будут запускаться в каждый период вызова onWrite. Может быть дешевле запускать функцию только для экземпляров onCreate и onDelete ваших конкретных коллекций. Как и в случае с базой данных noSQL, которую мы используем, повторяющийся код и данные могут сэкономить вам деньги.

Джонатан
источник
напишите об этом статью на носителе для облегчения доступа.
ahmadalibaloch
1

Решение с использованием разбивки на страницы с помощью offset& limit:

public int collectionCount(String collection) {
        Integer page = 0;
        List<QueryDocumentSnapshot> snaps = new ArrayList<>();
        findDocsByPage(collection, page, snaps);
        return snaps.size();
    }

public void findDocsByPage(String collection, Integer page, 
                           List<QueryDocumentSnapshot> snaps) {
    try {
        Integer limit = 26000;
        FieldPath[] selectedFields = new FieldPath[] { FieldPath.of("id") };
        List<QueryDocumentSnapshot> snapshotPage;
        snapshotPage = fireStore()
                        .collection(collection)
                        .select(selectedFields)
                        .offset(page * limit)
                        .limit(limit)
                        .get().get().getDocuments();    
        if (snapshotPage.size() > 0) {
            snaps.addAll(snapshotPage);
            page++;
            findDocsByPage(collection, page, snaps);
        }
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}
  • findDocsPage это рекурсивный метод поиска всех страниц коллекции

  • selectedFields для оптимизации запроса и получения только поля идентификатора вместо всего тела документа

  • limit максимальный размер каждой страницы запроса

  • page определить начальную страницу для разбивки на страницы

Судя по тестам, которые я провел, он хорошо работал для коллекций, содержащих примерно до 120 тысяч записей !

ℛɑƒæĿᴿᴹᴿ
источник
0

Мне потребовалось время, чтобы заставить это работать на основе некоторых из приведенных выше ответов, поэтому я подумал, что поделюсь им для использования другими. Надеюсь, это будет полезно.

'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

Это использует подсчет для создания числового уникального идентификатора. В моем использовании я никогда не буду уменьшать значение , даже если documentидентификатор, для которого требуется идентификатор, будет удален.

При collectionсоздании, которому требуется уникальное числовое значение

  1. Назначьте коллекцию appDataодним документом setс .docидентификаторомonly
  2. Установите uniqueNumericIDAmount0 вfirebase firestore console
  3. Использовать doc.data().uniqueNumericIDAmount + 1как уникальный числовой идентификатор
  4. Обновить appDataколлекцию uniqueNumericIDAmountс помощьюfirebase.firestore.FieldValue.increment(1)
firebase
    .firestore()
    .collection("appData")
    .doc("only")
    .get()
    .then(doc => {
        var foo = doc.data();
        foo.id = doc.id;

        // your collection that needs a unique ID
        firebase
            .firestore()
            .collection("uniqueNumericIDs")
            .doc(user.uid)// user id in my case
            .set({// I use this in login, so this document doesn't
                  // exist yet, otherwise use update instead of set
                phone: this.state.phone,// whatever else you need
                uniqueNumericID: foo.uniqueNumericIDAmount + 1
            })
            .then(() => {

                // upon success of new ID, increment uniqueNumericIDAmount
                firebase
                    .firestore()
                    .collection("appData")
                    .doc("only")
                    .update({
                        uniqueNumericIDAmount: firebase.firestore.FieldValue.increment(
                            1
                        )
                    })
                    .catch(err => {
                        console.log(err);
                    });
            })
            .catch(err => {
                console.log(err);
            });
    });
Ник Кардуччи
источник
0
var variable=0
variable=variable+querySnapshot.count

тогда, если вы хотите использовать его в переменной String, тогда

let stringVariable= String(variable)
Наталья Колиснык
источник
-1
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            int Counter = documentSnapshots.size();

        }
    });
Сампат Патро
источник
1
В этом ответе можно было бы использовать больше контекста для примера кода.
ShellNinja 01