Колекція хмарних Firestore


149

Чи можна порахувати, скільки предметів має колекція, використовуючи нову базу даних Firebase Cloud Cloudstore?

Якщо так, то як це зробити?


Відповіді:


190

Оновлення (квітень 2019 р.) - FieldValue.increment (див. Рішення про велику колекцію)


Як і в багатьох питаннях, відповідь така - Це залежить .

Ви повинні бути дуже обережними, обробляючи велику кількість даних на передній частині. Крім того, щоб передній вигляд відчував себе млявим, Firestore також стягує з вас 0,60 долара за мільйон прочитаних вами читань .


Невелика колекція (менше 100 документів)

Будьте обережні - користувальницький досвід Frontend може стати хітом

Поводження з цим на передньому кінці має бути прекрасним, доки ви не будете робити занадто багато логіки з цим повернутим масивом.

db.collection('...').get().then(snap => {
   size = snap.size // will return the collection size
});

Середня колекція (від 100 до 1000 документів)

Будьте обережні - виклики, прочитані Firestore, можуть коштувати дорого

Поводження з цим на передньому кінці неможливо, оскільки це має надто великий потенціал для уповільнення роботи користувачів. Ми повинні обробляти цей логічний сервер і повертати лише розмір.

Недолік цього методу полягає в тому, що ви все ще посилаєтесь на показання 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;
});

Тепер на фронті ви можете просто запитати це полеOfDocs для отримання розміру колекції.


17
Чудове рішення для великих колекцій! Я хотів би лише додати, що реалізатори повинні обгортати читання та запис у firestore.runTransaction { ... }блок. Це виправляє проблеми з одночасністю доступу numberOfDocs.
efemoney

2
Ці методи використовують перерахунок кількості записів. Якщо ви використовуєте лічильник і збільшуєте лічильник, використовуючи транзакцію, чи не вдалося б досягти такого ж результату без додаткових витрат і потреби хмарної функції?
користувач3836415

10
Рішення для великих колекцій не є безсильним і не працює в будь-яких масштабах. Тригери документів Firestore гарантовано запускаються принаймні один раз, але можуть працювати кілька разів. Коли це станеться, навіть підтримка оновлення всередині транзакції може запускатися не один раз, що дасть вам помилкове число. Коли я спробував це, я зіткнувся з проблемами з меншими десятками створення документів одночасно.
Тім Поллак

2
Привіт @TymPollack. Я помітив деяку непослідовну поведінку за допомогою хмарних тригерів. Будь-який шанс ви могли зв’язати мене зі статтею чи форумом, щоб пояснити поведінку, яку ви відчули?
Метью Маллін

2
@cmprogram ви читаєте всю колекцію та дані під час використання db.collection ('...') ... тож коли вам дані не потрібні, то ви праві - ви можете запросити запит на список ідентифікатори колекції (не дані колекційних документів), і вона вважається одним прочитаним.
атерешков

25

Найпростіший спосіб зробити це - прочитати розмір "querySnapshot".

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

Ви також можете прочитати довжину масиву документів всередині "querySnapshot".

querySnapshot.docs.length;

Або якщо "querySnapshot" порожній, прочитавши порожнє значення, яке поверне булеве значення.

querySnapshot.empty;

73
Майте на увазі, що кожен документ "коштує" одного прочитаного. Тож якщо ви порахуєте 100 предметів у колекції таким чином, вам стягується плата за 100 читань!
Георг

Правильно, але немає іншого способу підсумувати кількість документів у колекції. І якщо ви вже отримали колекцію, для читання масиву "docs" більше не потрібно буде отримувати, отже, не буде "коштувати" більше читання.
Омпель

5
Це читає всі документи на пам'ять! Удачі в цьому для великих наборів даних ...
Дан Даскалеску

85
це справді неймовірно, що Firebase Firestore не має такого роду db.collection.count(). Думаючи скинути їх лише заради цього
Blue Bot

8
Тим більше, що для великих колекцій не справедливо стягувати з нас так, ніби ми фактично завантажили та використали всі документи. Граф для таблиці (колекції) - така основна особливість. Враховуючи їхню модель ціноутворення та що Firestore був запущений у 2017 році, просто неймовірно, що Google не пропонує альтернативного способу отримати розмір колекції. Поки вони не реалізують його, вони повинні принаймні уникати стягнення плати за нього.
nibbana

23

Наскільки я знаю, для цього немає вбудованого рішення, і це можливо лише у вузлі sdk прямо зараз. Якщо у вас є

db.collection('someCollection')

ви можете використовувати

.select([fields])

визначити, яке поле ви хочете вибрати. Якщо ви зробите порожній select (), ви просто отримаєте масив посилань на документи.

приклад:

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

Це рішення є лише оптимізацією для найгіршого випадку завантаження всіх документів і не масштабується у великих колекціях!

Подивіться також на це:
Як отримати кількість документів у колекції з Cloud Firestore


На мій досвід, select(['_id'])швидше, ніжselect()
Янтон

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

Причина полягає в тому, що кожен тригер хмарних пожеж повинен бути ідентичним, як свідчить документація 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 в об'єкті контексту. Якщо функцію обробляли багато разів для однієї події, ідентифікатор події буде однаковим у всіх випадках. На жаль, у вашій базі даних має бути колекція "подій".


2
Вони формулюють це так, ніби така поведінка буде зафіксована у версії 1.0. Функції Amazon AWS страждають від тієї ж проблеми. Щось таке просте, як підрахунок полів стає складним і дорогим.
MarcG

Попробую це зараз, оскільки це здається кращим рішенням. Чи повертаєтесь ви чи очищаєте свою колекцію подій ніколи? Я думав просто додати поле дати та очистити старше, ніж на день, або щось просто для того, щоб набір даних був малим (можливо, 1 мільйон + події / день). Якщо тільки в FS не існує простого способу зробити це ... використовую лише FS кілька місяців.
Тим Поллак

1
Чи можемо ми перевірити, що context.eventIdзавжди буде однаково на кількох викликах одного і того ж тригера? У моєму тестуванні це здається послідовним, але я не можу знайти жодної "офіційної" документації, яка б це пояснила.
Майк Маклін

2
Отож, використовуючи це деякий час, я виявив, що, хоча це рішення працює з точно одним записом, що чудово, якщо занадто багато запускає вогонь із декількох документів, написаних одночасно, і намагаються оновити той самий рахунок документа, ви можете отримати помилки суперечки від firestore. Чи стикалися ви з цими, і як ви їх обійшли? (Помилка: 10 СТВОРЕНО: Занадто багато суперечок щодо цих документів. Будь ласка, спробуйте ще раз.)
Тим Поллак

1
@TymPollack погляд на розповсюджені лічильники записів документів обмежується приблизно одним оновленням в секунду
Джеймі

8

У 2020 році це все ще не доступне в Firebase SDK, однак воно доступне в розширеннях Firebase (бета-версія), проте налаштування та використання його досить складне ...

Розумний підхід

Допоміжники ... (створення / видалення здається зайвим, але дешевше, ніж 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'));

Чи не є це обмеженням "1 оновлення документа в секунду"?
Айяппа

Так, але це врешті-решт послідовно, тобто кількість підрахунків збірників з часом збігається з фактичним числом колекцій, це найпростіше рішення для впровадження, і у багатьох випадках короткий затримка в кількості є прийнятною.
Бен Віндінг

7

Я погоджуюся з @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 елементів, але тоді збереження цих масивів було б абсолютно некерованим. Крім того, у міру збільшення розміру doc читання та оновлення потребуватиме більше часу, що врешті-решт зробить будь-які інші операції над ним тимчасовими.
Поллак

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
Це рішення не є безсильним, тому будь-які тригери, які спрацьовують не один раз, скинуть вашу кількість оцінок та середній показник.
Поллак

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)
})

Вище ми створили документ з лічильниками імен, щоб зберігати всю інформацію про підрахунок. Ви можете оновити документ обліку таким чином: -

  • Створіть тригери firetore на кількість документів
  • Збільшення властивості count документа рахує під час створення нового документа.
  • Зменшення властивості count документа рахує під час видалення документа.

ціна wrt (читання документа = 1) та швидке пошуку даних вищезазначене рішення є хорошим.


3

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


2

Вирішення завдання полягає в тому, щоб:

запишіть лічильник у файл firebase doc, який ви збільшуєте в межах транзакції щоразу, коли створюєте новий запис

Ви зберігаєте підрахунок у полі вашого нового запису (тобто: позиція: 4).

Потім ви створюєте індекс у цьому полі (позиція DESC).

Ви можете зробити пропуск + обмеження за допомогою запиту.Where ("позиція", "<" x) .OrderBy ("позиція", DESC)

Сподіваюся, це допомагає!


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 (запланована функція) для видалення старих подій, щоб заощадити гроші на сховищі бази даних. Вам потрібен принаймні план спалаху, а може бути ще якась конфігурація. Наприклад, ви можете запускати його щонеділі об 11: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();

Який автоматично оновлює записиCount у користувальницькому Документі. Таким чином, ви можете легко додати ще одне до багатьох підрахунків. Це просто дає вам уявлення про те, як ви можете автоматизувати речі. Я також дав вам інший спосіб видалити події. Ви повинні прочитати кожну дату, щоб видалити її, тому вона не дійсно заощадить вас, щоб видалити їх пізніше, просто робить функцію повільніше.

/**
 * 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, яку ми використовуємо, повторний код і дані можуть заощадити гроші.


написати статтю про нього на носії для легкого доступу.
ахмадалібалоч


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

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


Будь ласка, включіть відповідні реквізити та код у саму відповідь , вказуючи людей на публікації вашого блогу, насправді не є суть StackOverflow.
DBS

0

Цей запит призведе до підрахунку документа.

this.db.collection(doc).get().subscribe((data) => {
      count = data.docs.length;
    });

console.log(count)

1
Не гарне рішення, оскільки ви отримуєте всі документи з колекції кожного разу. Це коштуватиме багато. Кращим підходом є встановлення лічильника щоразу, коли в цю колекцію додається новий документ, щоб ви могли просто отримати один документ замість кількох тисяч.
Corentin Houdayer

0

При цьому використовується підрахунок для створення цифрового унікального ідентифікатора. У моєму використанні я не буду декрементувати ніколи , навіть коли documentвидалено те, для чого потрібен ідентифікатор.

Після collectionстворення, яке потребує унікального числового значення

  1. Позначте колекцію appDataз одним документом, setз .docідентифікаторомonly
  2. Встановити uniqueNumericIDAmount 0 у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);
            });
    });

-1
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            int Counter = documentSnapshots.size();

        }
    });

1
Ця відповідь може використовувати більше контексту щодо прикладу коду.
ShellNinja
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.