Contagem de coleta do Cloud Firestore


Respostas:


189

Atualização (abril de 2019) - FieldValue.increment (consulte solução de coleção grande)


Como em muitas perguntas, a resposta é - depende .

Você deve ter muito cuidado ao lidar com grandes quantidades de dados no front-end. Além de tornar seu front end lento, o Firestore também cobra US $ 0,60 por milhão de leituras que você faz.


Pequena coleção (menos de 100 documentos)

Use com cuidado - a experiência do usuário do front-end pode sofrer um impacto

Lidar com isso no front-end deve ser bom, desde que você não faça muita lógica com essa matriz retornada.

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

Coleção média (100 a 1000 documentos)

Use com cuidado - as chamadas de leitura do Firestore podem custar muito

Lidar com isso no front end não é viável, pois tem muito potencial para desacelerar o sistema dos usuários. Devemos lidar com esse lado do servidor lógico e retornar apenas o tamanho.

A desvantagem desse método é que você ainda está invocando leituras do firestore (iguais ao tamanho da sua coleção), que a longo prazo podem acabar custando mais do que o esperado.

Função de nuvem:

...
db.collection('...').get().then(snap => {
    res.status(200).send({length: snap.size});
});

A parte dianteira:

yourHttpClient.post(yourCloudFunctionUrl).toPromise().then(snap => {
     size = snap.length // will return the collection size
})

Grande coleção (mais de 1000 documentos)

Solução mais escalável


FieldValue.increment ()

A partir de abril de 2019, o Firestore agora permite incrementar contadores, completamente atomicamente e sem ler os dados antes . Isso garante que temos valores corretos de contador, mesmo quando atualizamos de várias fontes simultaneamente (resolvidas anteriormente usando transações), além de reduzir o número de leituras de banco de dados que realizamos.


Ao ouvir qualquer documento excluído ou criado, podemos adicionar ou remover de um campo de contagem localizado no banco de dados.

Consulte os documentos do firestore - Contadores distribuídos ou dê uma olhada na agregação de dados por Jeff Delaney. Seus guias são realmente fantásticos para quem usa o AngularFire, mas suas lições devem passar para outras estruturas também.

Função de nuvem:

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

Agora, no front-end, você pode consultar este campo numberOfDocs para obter o tamanho da coleção.


17
Ótima solução para grandes coleções! Gostaria apenas de acrescentar que os implementadores devem agrupar a leitura e gravação em um firestore.runTransaction { ... }bloco. Isso corrige problemas de concorrência com o acesso numberOfDocs.
Efemoney

2
Esses métodos estão usando uma recontagem do número de registros. Se você usar um contador e incrementá-lo usando uma transação, isso não alcançaria o mesmo resultado sem o custo adicional e a necessidade de uma função de nuvem?
user3836415

10
A solução para grandes coleções não é idempotente e não funciona em nenhuma escala. É garantido que os acionadores de documentos do Firestore sejam executados pelo menos uma vez, mas podem executar várias vezes. Quando isso acontece, mesmo a manutenção da atualização dentro de uma transação pode ser executada mais de uma vez, o que fornecerá um número falso. Quando tentei, tive problemas com menos de uma dúzia de criações de documentos por vez.
Tym Pollack

2
Olá @TymPollack. Percebi algum comportamento inconsistente usando gatilhos de nuvem. Alguma chance de você me vincular a um artigo ou fórum para explicar o comportamento que você experimentou?
Matthew Mullin

2
@cmprogram você está lendo toda a coleção e os dados quando usa db.collection ('...') ... então, quando você não precisa dos dados, está certo - você pode facilmente solicitar uma lista de IDs de coleta (não coleta dados de documentos) e conta como uma leitura.
atereshkov 4/09/19

24

A maneira mais simples de fazer isso é ler o tamanho de um "querySnapshot".

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

Você também pode ler o comprimento da matriz de documentos em "querySnapshot".

querySnapshot.docs.length;

Ou se um "querySnapshot" estiver vazio lendo o valor vazio, que retornará um valor booleano.

querySnapshot.empty;

73
Esteja ciente de que cada documento "custa" uma leitura. Portanto, se você contar 100 itens em uma coleção dessa maneira, será cobrado por 100 leituras!
Georg

Correto, mas não há outra maneira de resumir o número de documentos em uma coleção. E se você já buscou a coleção, a leitura da matriz "docs" não exigirá mais buscas, portanto, não "custará" mais leituras.
Ompel

5
Isso lê todos os documentos na memória! Boa sorte com isso para grandes conjuntos de dados ...
Dan Dascalescu

84
isso é realmente inacreditável que o Firebase Firestore não tenha db.collection.count(). Pensando em largá-los apenas por isso #
Blue Bot

8
Especialmente para coleções grandes, não é justo nos cobrar como se realmente tivéssemos baixado e usado todos os documentos. Contar para uma tabela (coleção) é um recurso tão básico. Considerando o modelo de preços e o Firestore foi lançado em 2017, é incrível que o Google não forneça uma maneira alternativa de obter o tamanho de uma coleção. Até que não o implementem, eles devem pelo menos evitar cobrar por isso.
Nibbana

23

Até onde eu sei, não existe uma solução incorporada para isso e só é possível no nó sdk no momento. Se você tem um

db.collection('someCollection')

você pode usar

.select([fields])

para definir qual campo você deseja selecionar. Se você selecionar vazio (), obterá apenas uma série de referências a documentos.

exemplo:

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

Esta solução é apenas uma otimização para o pior caso de baixar todos os documentos e não é escalável em grandes coleções!

Veja também:
Como obter uma contagem do número de documentos em uma coleção com o Cloud Firestore


Na minha experiência, select(['_id'])é mais rápido do queselect()
JAnton 17/06

13

Tenha cuidado ao contar o número de documentos para grandes coleções . É um pouco complexo com o banco de dados do firestore se você deseja ter um contador pré-calculado para cada coleção.

Código como este não funciona neste caso:

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

O motivo é que cada acionador do firestore na nuvem precisa ser idempotente, como a documentação do firestore diz: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees

Solução

Portanto, para impedir várias execuções do seu código, você precisa gerenciar com eventos e transações. Esta é minha maneira particular de lidar com grandes contadores de coleções:

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

Casos de uso aqui:

/**
 * 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'));

Como você pode ver, a chave para impedir a execução múltipla é a propriedade chamada eventId no objeto de contexto. Se a função tiver sido manipulada várias vezes para o mesmo evento, o ID do evento será o mesmo em todos os casos. Infelizmente, você deve ter a coleção "events" em seu banco de dados.


2
Eles estão formulando isso como se esse comportamento fosse corrigido na versão 1.0. As funções do Amazon AWS sofrem do mesmo problema. Algo tão simples como contar campos se torna complexo e caro.
23418 MarcG # 2328

Vou tentar isso agora, pois parece uma solução melhor. Você volta e limpa sua coleção de eventos? Eu estava pensando em adicionar um campo de data e remover mais de um dia ou algo assim apenas para manter o conjunto de dados pequeno (possivelmente 1mil + eventos / dia). A menos que exista uma maneira fácil no FS de fazer isso ... use o FS apenas alguns meses.
precisa

1
Podemos verificar que context.eventIdsempre será o mesmo em várias invocações do mesmo gatilho? Nos meus testes, parece ser consistente, mas não consigo encontrar nenhuma documentação "oficial" afirmando isso.
Mike McLin 4/01/19

2
Então, depois de usar isso por um tempo, descobri que, embora essa solução funcione com exatamente uma gravação, o que é ótimo, se muitos gatilhos forem disparados de vários documentos sendo gravados ao mesmo tempo e tentando atualizar o mesmo documento de contagem, você poderá obtenha erros de contenção do firestore. Você já os encontrou e como conseguiu contornar isso? (. Erro: 10 ABORTED: Too muita disputa sobre estes documentos Por favor tente novamente.)
Tym Pollack

1
@TymPollack olhada contadores distribuídos redige o original são limitados a cerca de uma atualização por segundo
Jamie

8

Em 2020, isso ainda não está disponível no Firebase SDK, no entanto, está disponível no Firebase Extensions (Beta), no entanto, é bastante complexo configurar e usar ...

Uma abordagem razoável

Ajudantes ... (criar / excluir parece redundante, mas é mais barato que o 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;
}

Ganchos de Firestore exportados


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

Em ação

A coleção raiz do edifício e todas as sub-coleções serão rastreadas.

insira a descrição da imagem aqui

Aqui sob o /counters/caminho da raiz

insira a descrição da imagem aqui

Agora a contagem de coleções será atualizada automaticamente e, eventualmente! Se você precisar de uma contagem, basta usar o caminho da coleção e prefixá-lo counters.

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
  .doc('counters/' + collectionPath)
  .get()
  .then(snap => snap.get('count'));

Isso não está sujeito à mesma limitação "1 atualização de documento por segundo"?
Ayyappa

Sim, mas é eventualmente consistente, o que significa que a contagem de coleta eventualmente se alinhará com a contagem real de coleções, é a solução mais fácil de implementar e, em muitos casos, um pequeno atraso na contagem é aceitável.
Ben Winding

7

Concordo com @ Matthew, vai custar muito se você realizar essa consulta.

[CONSELHOS PARA DESENVOLVEDORES ANTES DE INICIAR SEUS PROJETOS]

Como previmos essa situação no início, podemos realmente fazer uma coleção, ou seja, contadores com um documento para armazenar todos os contadores em um campo com o tipo number.

Por exemplo:

Para cada operação CRUD na coleção, atualize o documento do contador:

  1. Quando você cria uma nova coleção / sub-coleção: (+1 no contador) [1 operação de gravação]
  2. Quando você exclui uma coleção / sub-coleção: (-1 no contador) [1 operação de gravação]
  3. Quando você atualiza uma coleção / subcoleção existente, não faça nada no documento do contador: (0)
  4. Ao ler uma coleção / subcoleção existente, não faça nada no documento do contador: (0)

Da próxima vez, quando você quiser obter o número de coleção, basta consultar / apontar para o campo do documento. [1 operação de leitura]

Além disso, você pode armazenar o nome das coleções em uma matriz, mas isso será complicado, a condição da matriz na firebase é mostrada abaixo:

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

Portanto, se você não excluir a coleção, poderá usar a matriz para armazenar a lista de nomes de coleções em vez de consultar toda a coleção todas as vezes.

Espero que ajude!


Para uma pequena coleção, talvez. Mas lembre-se de que o limite de tamanho do documento do Firestore é de ~ 1 MB, que, se os IDs do documento em uma coleção forem gerados automaticamente (20 bytes), você poderá armazenar apenas 52.425 deles antes do documento que contém a matriz é muito grande. Eu acho que, como solução alternativa, você pode criar um novo documento a cada 50.000 elementos, mas a manutenção dessas matrizes seria totalmente incontrolável. Além disso, à medida que o tamanho do documento aumenta, a leitura e a atualização demoram mais tempo, o que acabará por fazer com que outras operações expirem.
precisa saber é o seguinte

5

Não, não há suporte interno para consultas de agregação no momento. No entanto, existem algumas coisas que você poderia fazer.

O primeiro está documentado aqui . Você pode usar transações ou funções da nuvem para manter as informações agregadas:

Este exemplo mostra como usar uma função para acompanhar o número de classificações em uma subcoleção, bem como a classificação média.

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

A solução mencionada pela jbb também é útil se você quiser contar documentos com pouca frequência. Certifique-se de usar a select()instrução para evitar o download de todos os documentos (é muita largura de banda quando você precisa apenas de uma contagem). select()está disponível apenas nos SDKs do servidor por enquanto, para que a solução não funcione em um aplicativo móvel.


1
Essa solução não é idempotente; portanto, qualquer gatilho que disparar mais de uma vez descartará seu número de classificações e a média.
precisa saber é o seguinte

4

Não há opção direta disponível. Você não pode fazer db.collection("CollectionName").count(). Abaixo estão as duas maneiras pelas quais você pode encontrar a contagem do número de documentos em uma coleção.

1: - Obtenha todos os documentos da coleção e obtenha seu tamanho (não é a melhor solução)

db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})

Ao usar o código acima, as leituras de seu documento serão iguais ao tamanho dos documentos em uma coleção e é por isso que é necessário evitar o uso da solução acima.

2: - Crie um documento separado em sua coleção que armazene a contagem do número de documentos na coleção. (Melhor solução)

db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})

Acima, criamos um documento com contagem de nomes para armazenar todas as informações de contagem. Você pode atualizar o documento de contagem da seguinte maneira: -

  • Criar disparadores de firestore nas contagens de documentos
  • Incremente a propriedade count do documento count quando um novo documento é criado.
  • Reduza a propriedade count do documento count quando um documento for excluído.

preço wrt (leitura de documento = 1) e recuperação rápida de dados, a solução acima é boa.


3

Incremente um contador usando 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),
    })
  );

Neste exemplo, incrementamos um instanceCountcampo no projeto cada vez que um documento é adicionado à instancessub-coleção. Se o campo ainda não existir, ele será criado e incrementado para 1.

A incrementação é transacional internamente, mas você deve usar um contador distribuído se precisar incrementar com mais frequência do que a cada 1 segundo.

Geralmente, é preferível implementar onCreatee, em onDeletevez de onWritesolicitar onWriteatualizações, o que significa que você está gastando mais dinheiro em invocações de funções desnecessárias (se você atualizar os documentos em sua coleção).


2

Uma solução alternativa é:

escreva um contador em um documento da base de firmas, que você incrementa em uma transação toda vez que cria uma nova entrada

Você armazena a contagem em um campo da sua nova entrada (ou seja: posição: 4).

Em seguida, você cria um índice nesse campo (posição DESC).

Você pode fazer um salto + limite com uma consulta.Where ("position", "<" x) .OrderBy ("position", DESC)

Espero que isto ajude!


1

Criei uma função universal usando todas essas idéias para lidar com todas as situações de contador (exceto consultas).

A única exceção seria ao fazer tantas gravações por segundo, que você fica mais lento. Um exemplo seria curtir em uma postagem de tendência. É um exagero em uma postagem de blog, por exemplo, e vai custar mais. Sugiro que você crie uma função separada usando shards: 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);
        });;
    }
}

Isso lida com eventos, incrementos e transações. A vantagem disso é que, se você não tiver certeza da precisão de um documento (provavelmente ainda em beta), poderá excluir o contador para que ele os adicione automaticamente no próximo gatilho. Sim, isso custa, portanto, não o exclua.

O mesmo tipo de coisa para obter a contagem:

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const colSnap = await db.doc('_counters/' + collectionPath).get();
const count = colSnap.get('count');

Além disso, convém criar um trabalho cron (função agendada) para remover eventos antigos e economizar dinheiro no armazenamento do banco de dados. Você precisa de pelo menos um plano de incêndio, e pode haver mais configurações. Você pode executá-lo todos os domingos às 23h, por exemplo. https://firebase.google.com/docs/functions/schedule-functions

Isso não foi testado , mas deve funcionar com alguns ajustes:

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

E por último, não se esqueça de proteger as coleções no firestore.rules :

match /_counters/{document} {
  allow read;
  allow write: if false;
}
match /_events/{document} {
  allow read, write: if false;
}

Atualização: consultas

Adicionando à minha outra resposta, se você deseja automatizar as contagens de consultas, também pode usar este código modificado na sua função de nuvem:

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

O qual atualizará automaticamente o postsCount no userDocument. Você pode facilmente adicionar outro a muitas contagens dessa maneira. Isso fornece idéias de como você pode automatizar as coisas. Também lhe dei outra maneira de excluir os eventos. Você precisa ler cada data para excluí-la, para que não seja realmente necessário excluí-las mais tarde, apenas torna a função mais lenta.

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

Também devo avisar que funções universais serão executadas a cada período de chamada onWrite. Pode ser mais barato executar apenas a função nas instâncias onCreate e onDelete de suas coleções específicas. Como o banco de dados noSQL que estamos usando, códigos e dados repetidos podem economizar seu dinheiro.


escreva um artigo sobre ele no meio para facilitar o acesso.
ahmadalibaloch


0

Levei um tempo para que isso funcionasse com base em algumas das respostas acima, então pensei em compartilhar para outras pessoas usarem. Espero que seja útil.

'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

Eu tentei muito com diferentes abordagens. E, finalmente, aperfeiçoo um dos métodos. Primeiro, você precisa criar uma coleção separada e salvar todos os eventos. Segundo, você precisa criar um novo lambda para ser acionado pelo tempo. Este lambda Contará eventos na coleção de eventos e limpará os documentos do evento. Detalhes do código no artigo. https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca


Inclua os detalhes e o código relevantes na própria resposta , apontar pessoas para as postagens do seu blog não é realmente o objetivo do StackOverflow.
DBS

0

Esta consulta resultará na contagem de documentos.

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

console.log(count)

1
Não é uma boa solução, pois você sempre busca todos os documentos da coleção. Vai custar muito. Uma abordagem melhor é configurar um contador toda vez que um novo documento for adicionado a essa coleção, para que você possa simplesmente buscar um documento em vez de alguns milhares.
Corentin Houdayer 17/07

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

            int Counter = documentSnapshots.size();

        }
    });

1
Esta resposta pode usar mais contexto quanto ao exemplo de código.
precisa saber é o seguinte
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.