Cloud Firestore toplama sayısı


Yanıtlar:


190

Güncellemesi (Nisan 2019) - FieldValue.increment (büyük toplama çözümüne bakın)


Birçok soruda olduğu gibi, cevap - O bağlıdır .

Ön uçta büyük miktarda veri tutarken çok dikkatli olmalısınız. Ön uçunuzu ağır hissettirmenin yanı sıra , Firestore ayrıca yaptığınız milyon okuma başına 0,60 dolar alıyor .


Küçük koleksiyon (100'den az belge)

Dikkatli kullanın - Kullanıcı arabirimi kullanıcısı deneyimi isabet alabilir

Bu döndürülen dizi ile çok fazla mantık yapmadığınız sürece bunu ön uçta işlemek iyi olmalıdır.

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

Orta boy toplama (100 ila 1000 doküman)

Dikkatli kullanın - Firestore okuma çağrıları çok pahalıya mal olabilir

Bunu kullanıcı tarafında yavaşlatmak için çok fazla potansiyele sahip olduğundan ön uçta işlem yapmak uygun değildir. Bu mantık sunucusu tarafını işlemeli ve yalnızca boyutu döndürmeliyiz.

Bu yöntemin dezavantajı, hala uzun süre beklediğinizden daha pahalıya mal olabilecek firestore okumalarını (koleksiyonunuzun boyutuna eşit) çağırmanızdır.

Bulut İşlevi:

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

Başlangıç ​​aşaması:

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

Büyük koleksiyon (1000+ belge)

En ölçeklenebilir çözüm


FieldValue.increment ()

Nisan 2019 itibariyle Firestore, sayaçları tamamen atomik olarak ve daha önce verileri okumadan artırmaya izin veriyor . Bu, aynı anda birden fazla kaynaktan güncelleme yaparken (daha önce işlemler kullanılarak çözülmüşken) doğru sayaç değerlerine sahip olmamızı sağlarken, gerçekleştirdiğimiz veritabanı okumalarının sayısını da azaltır.


Herhangi bir belgeyi dinleyerek siler veya oluşturur, veritabanında oturan bir sayım alanına ekleyebilir veya kaldırabilirsiniz.

Firestore belgelerine bakın - Dağıtılmış Sayıcılar Veya Jeff Delaney tarafından Veri Toplamaya bir göz atın . Rehberleri AngularFire kullanan herkes için gerçekten harika ama dersleri başka çerçevelere de taşınmalı.

Bulut İşlevi:

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

Şimdi ön uçta koleksiyonun boyutunu almak için bu numberOfDocs alanını sorgulayabilirsiniz.


17
Büyük koleksiyonlar için mükemmel çözüm! Sadece uygulayıcıların okuma ve yazma işlemlerini bir firestore.runTransaction { ... }blok halinde tamamlamaları gerektiğini eklemek isterim . Bu, erişimle ilgili eşzamanlılık sorunlarını giderir numberOfDocs.
efemoney

2
Bu yöntemler, kayıt sayısını yeniden saymayı kullanır. Bir sayaç kullanırsanız ve sayacı bir işlem kullanarak artırırsanız, bir bulut işlevinin ek maliyeti ve gereksinimi olmadan aynı sonucu elde etmez misiniz?
user3836415

10
Büyük koleksiyonlar için çözüm idempotent değildir ve herhangi bir ölçekte çalışmaz. Firestore belge tetikleyicilerinin en az bir kez çalışması garanti edilir, ancak birden çok kez çalıştırılabilir. Bu durumda, güncellemenin bir işlem içinde tutulması bile birden fazla kez çalıştırılabilir ve bu size yanlış bir sayı verir. Bunu denediğimde, bir seferde bir düzineden daha az belge yaratımı ile karşılaştım.
Tym Pollack

2
Merhaba @ Timpollack. Bulut tetikleyicileri kullanarak bazı tutarsız davranışlar fark ettim. Yaşadığınız davranışı açıklamak için beni ve makaleyi ya da forumu bağlantılandırma şansınız oldu mu?
Matthew Mullin

2
@ cmprogram db.collection ('...') kullanırken tüm koleksiyonu ve verileri okuyorsunuz ... bu nedenle verilere ihtiyacınız olmadığında haklısınız - kolayca bir liste isteyebilirsiniz koleksiyon kimlikleri (koleksiyon belgeleri verisi değil) ve tek bir okuma olarak sayılır.
atereshkov

25

Bunu yapmanın en basit yolu bir "querySnapshot" boyutunu okumaktır.

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

"QuerySnapshot" içindeki docs dizisinin uzunluğunu da okuyabilirsiniz.

querySnapshot.docs.length;

Veya bir boolean değeri döndürecek boş değeri okuyarak bir "querySnapshot" boşsa.

querySnapshot.empty;

73
Her belgenin bir okumanın "ücretlendirildiğini" unutmayın. Bir koleksiyondaki 100 öğeyi bu şekilde sayarsanız, 100 okuma için ücretlendirilirsiniz!
Georg

Doğru, ancak bir koleksiyondaki belge sayısını özetlemenin başka bir yolu yok. Ve koleksiyonu zaten getirdiyseniz, "dokümanlar" dizisini okumak daha fazla getirme gerektirmez, bu nedenle daha fazla okumaya "mal olmaz".
Ompel

5
Bu bellekteki tüm belgeleri okur! Büyük veri setleri için iyi şanslar ...
Dan Dascalescu

85
Firebase Firestore'un böyle bir şeyi olmadığı gerçekten inanılmaz db.collection.count(). Onları sadece bunun için bırakmayı düşünüyorum
Mavi Bot

8
Özellikle büyük koleksiyonlar için, tüm belgeleri gerçekten indirip kullandığımızdan ücret almak adil değildir. Bir tablo (koleksiyon) için saymak böyle temel bir özelliktir. Fiyatlandırma modellerini ve Firestore'un 2017'de piyasaya sürüldüğünü düşünürsek, Google'ın bir koleksiyonun boyutunu almak için alternatif bir yol sunmaması inanılmaz. Uygulamaya kadar, en azından bunun için şarj etmekten kaçınmalıdırlar.
nibbana

23

Bildiğim kadarıyla bunun için yerleşik bir çözüm yoktur ve sadece şu anda sdk düğümünde mümkündür. Eğer bir

db.collection('someCollection')

kullanabilirsiniz

.select([fields])

hangi alanı seçmek istediğinizi tanımlamak için. Boş bir select () yaparsanız, bir dizi belge başvurusu alırsınız.

misal:

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

Bu çözüm yalnızca tüm belgeleri indirmenin en kötü durumu için bir optimizasyondur ve büyük koleksiyonlarda ölçeklenmez!

Ayrıca şuna da göz atın:
Cloud Firestore ile bir koleksiyondaki sayıdaki belge sayısını alma


Benim durumumda, select(['_id'])daha hızlıselect()
JAnton

13

Büyük koleksiyonlar için doküman sayısını saymaya dikkat edin . Her koleksiyon için önceden hesaplanmış bir sayaç istiyorsanız, firestore veritabanı ile biraz karmaşıktır.

Böyle bir kod bu durumda çalışmaz:

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

Bunun nedeni, yangın deposu belgelerinin dediği gibi her bulut firestore tetikleyicisinin idempotent olması gerektiğidir: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees

Çözüm

Bu nedenle, kodunuzun birden çok yürütülmesini önlemek için olaylar ve işlemlerle yönetmeniz gerekir. Bu benim büyük toplama sayaçları işlemek için benim özel yoludur:

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

Buradaki vakaları kullanın:

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

Gördüğünüz gibi, birden fazla yürütmeyi önlemenin anahtarı , bağlam nesnesindeki eventId adı verilen özelliktir . İşlev aynı olay için birçok kez işlenmişse, olay kimliği her durumda aynı olacaktır. Maalesef, veritabanınızda "etkinlikler" koleksiyonunuzun olması gerekir.


2
Bunu, 1.0 sürümünde bu davranış düzeltilmiş gibi ifade ediyorlar. Amazon AWS işlevleri aynı sorundan muzdariptir. Alanların sayılması kadar basit bir şey karmaşık ve pahalı hale gelir.
MarcG

Şimdi daha iyi bir çözüm gibi göründüğü için bunu deneyecek. Geri dönüp etkinlik koleksiyonunuzu temizliyor musunuz? Ben sadece bir tarih alanı ekleyerek ve sadece küçük veri kümesi (muhtemelen 1mil + olay / gün) tutmak için bir günden daha eski tasfiye düşünüyorum. FS'de bunu yapmanın kolay bir yolu olmadığı sürece ... FS'yi sadece birkaç ay kullanıyor.
Tym Pollack

1
context.eventIdAynı tetikleyicinin birden çok çağrısında bunun her zaman aynı olacağını doğrulayabilir miyiz ? Testlerimde tutarlı görünüyor, ancak bunu belirten herhangi bir "resmi" belge bulamıyorum.
Mike McLin

2
Bunu bir süre kullandıktan sonra, bu çözümün tam olarak bir yazma ile çalıştığını fark ettim, bu da harika, eğer çok sayıda tetikleyici bir kerede yazılıyor ve aynı sayı belgesini güncellemeye çalışıyorsa, Firestore'dan çekişme hataları alın. Bunlarla karşılaştınız ve nasıl başa çıktınız? (Hata: 10 ABORTED: Bu belgeler üzerinde çok fazla çekişme var. Lütfen tekrar deneyin.)
Tym Pollack

1
@TymPollack dağıtılmış sayaçlara belge doküman yazma saniyede yaklaşık bir güncelleme ile sınırlıdır
Jamie

8

2020'de bu Firebase SDK'sında hala mevcut değildir, ancak Firebase Uzantıları'nda (Beta) mevcuttur, ancak kurulumu ve kullanımı oldukça karmaşıktır ...

Makul bir yaklaşım

Yardımcıları ... (oluşturma / silme gereksiz görünüyor ancak onUpdate'den daha ucuz)

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

İhraç edilen Firestore kancaları


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

Eylemde

Yapı kökü koleksiyonu ve tüm alt koleksiyonlar izlenecektir.

resim açıklamasını buraya girin

Burada /counters/kök yolunun altında

resim açıklamasını buraya girin

Şimdi toplama sayıları otomatik ve sonunda güncellenecek! Bir sayıya ihtiyacınız varsa, toplama yolunu kullanın ve önekini ekleyin counters.

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

Bu, "Saniyede 1 belge güncellemesi" ile aynı sınırlamaya tabi tutulmuyor mu?
Ayyappa

Evet, ama sonuçta tutarlı, yani toplama sayısı sonunda gerçek toplama sayısına hizalanacak, uygulanması en kolay çözüm ve çoğu durumda kısa bir gecikme kabul edilebilir.
Ben Sarma

7

@Matthew ile hemfikirim, böyle bir sorgu yaparsanız çok maliyetli olacaktır .

[PROJELERİNE BAŞLAMADAN ÖNCE GELİŞTİRİCİLER İÇİN TAVSİYE]

Başlangıçta bu durumu öngördüğümüz için, aslında tüm sayaçları tür içeren bir alanda saklamak için bir belgeyle bir sayaç gibi bir koleksiyon yapabiliriz number.

Örneğin:

Koleksiyondaki her CRUD işlemi için sayaç belgesini güncelleyin:

  1. Ne zaman yaratmak , yeni bir koleksiyon / subcollection: (sayacında +1) [1 yazma işlemi]
  2. Bir koleksiyonu / alt koleksiyonu sildiğinizde : (sayaçta -1) [1 yazma işlemi]
  3. Ne zaman güncellemek varolan toplama / subcollection, sayaç belge üzerinde hiçbir şey: (0)
  4. Ne zaman okumak varolan toplama / subcollection, sayaç belge üzerinde hiçbir şey: (0)

Bir dahaki sefere, koleksiyon sayısını almak istediğinizde, belge alanını sorgulamanız / işaret etmeniz yeterlidir. [1 okuma işlemi]

Ayrıca, koleksiyon adını bir dizide saklayabilirsiniz, ancak bu zor olacaktır, firebase'deki dizinin durumu aşağıdaki gibi gösterilir:

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

Bu nedenle, koleksiyonu silmeyecekseniz, her seferinde tüm koleksiyonu sorgulamak yerine koleksiyon adı listesini saklamak için dizi kullanabilirsiniz.

Umarım yardımcı olur!


Belki küçük bir koleksiyon için. Ancak, Firestore belge boyutu sınırının ~ 1 MB olduğunu unutmayın; bu, bir koleksiyondaki belge kimlikleri otomatik olarak oluşturulduysa (20 bayt), diziyi tutan dokümandan yalnızca ~ 52,425 tanesini depolayabileceğinizi unutmayın. çok büyük. Sanırım bir çözüm olarak her 50.000 elementte yeni bir belge oluşturabilirsiniz, ancak daha sonra bu dizileri korumak tamamen yönetilemez olacaktır. Ayrıca, belge boyutu büyüdükçe, okunması ve güncellenmesi daha uzun sürecektir, bu da sonunda başka bir işlemin çekişme zaman aşımına uğrayacaktır.
Tym Pollack

5

Hayır, şu anda toplama sorguları için yerleşik bir destek yok. Ancak yapabileceğiniz birkaç şey var.

İlki burada belgelenmiştir . Toplu bilgileri korumak için işlemleri veya bulut işlevlerini kullanabilirsiniz:

Bu örnek, bir alt toplamadaki derecelendirme sayısını ve ortalama derecelendirmeyi izlemek için bir işlevin nasıl kullanılacağını gösterir.

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'nun bahsettiği çözüm, belgeleri nadiren saymak istiyorsanız da yararlıdır. select()Her belgeyi indirmekten kaçınmak için ifadeyi kullandığınızdan emin olun (bu yalnızca bir sayıya ihtiyacınız olduğunda çok fazla bant genişliğidir). select()şimdilik sunucu SDK'larında kullanılabilir, böylece çözüm bir mobil uygulamada çalışmaz.


1
Bu çözüm idempotent değildir, bu nedenle birden fazla kez tetiklenen tetikleyiciler derecelendirme ve ortalama sayınızı atacaktır.
Tym Pollack

4

Doğrudan seçenek yoktur. Yapamazsın db.collection("CollectionName").count(). Bir koleksiyondaki belge sayısını bulabilmenin iki yolu aşağıdadır.

1: - Koleksiyondaki tüm belgeleri alın ve ardından boyutunu alın. (En iyi çözüm değil)

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

Yukarıdaki kodu kullanarak, belge okumalarınız bir koleksiyondaki belgelerin boyutuna eşit olacaktır ve bu yüzden yukarıdaki çözümü kullanmaktan kaçınmanız gerekir.

2: - Koleksiyonunuzda, koleksiyondaki belge sayısını saklayacak ayrı bir belge oluşturun. (En İyi Çözüm)

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

Yukarıda, tüm sayım bilgilerini saklamak için ad sayımlarına sahip bir belge oluşturduk. Sayım belgesini aşağıdaki şekilde güncelleyebilirsiniz: -

  • Belge sayılarında bir yangın deposu tetikleyicileri oluşturma
  • Yeni bir belge oluşturulduğunda sayım belgesinin count özelliğini artırın.
  • Bir belge silindiğinde sayım belgesinin count özelliğini azaltın.

wrt fiyat (Belge Okuma = 1) ve yukarıdaki çözüm hızlı veri alma iyidir.


3

Admin.firestore.FieldValue.increment komutunu kullanarak bir sayacı artırın :

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

Bu örnekte instanceCount, instancesalt koleksiyona her belge eklendiğinde projedeki bir alanı artırıyoruz . Alan henüz mevcut değilse, oluşturulacak ve 1'e artırılacaktır.

Artım dahili olarak işlemseldir, ancak her 1 saniyeden daha sık artırmanız gerekiyorsa dağıtılmış bir sayaç kullanmalısınız .

Uygulamayı uygulamak genellikle tercih edilir onCreateve güncellemeleri çağırmak onDeleteyerine gereksiz işlev çağrıları için daha fazla para harcadığınız anlamına gelir (koleksiyonunuzdaki dokümanları güncellerseniz).onWriteonWrite


2

Çözüm:

her yeni giriş oluşturduğunuzda bir işlemde artırdığınız bir ateş tabanı belgesine sayaç yazma

Sayımı yeni girişinizin bir alanında saklarsınız (örneğin: konum: 4).

Sonra bu alanda bir dizin oluşturun (DESC konumu).

Bir sorgu ile bir atlama + sınırı yapabilirsiniz. ("Position", "<" x) .OrderBy ("position", DESC)

Bu yardımcı olur umarım!


1

Tüm sayaç durumlarını (sorgular hariç) ele almak için tüm bu fikirleri kullanarak evrensel bir işlev oluşturdum.

Tek istisna, bu kadar çok yazar bir saniye yazarken sizi yavaşlatır. Bir örnek, popüler bir gönderideki beğeniler olabilir . Örneğin, bir blog yazısında aşırıya kaçıyor ve size daha pahalıya mal olacak. Bu durumda kırıkları kullanarak ayrı bir işlev oluşturmanızı öneririm: 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);
        });;
    }
}

Bu olayları, artışları ve işlemleri yönetir. Bunun güzelliği, bir belgenin doğruluğundan emin değilseniz (muhtemelen hala betadayken), bir sonraki tetikleyiciye otomatik olarak eklemesi için sayacı silebilirsiniz. Evet, bu maliyet, bu yüzden başka şekilde silmeyin.

Sayımı almak için aynı tür şeyler:

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

Ayrıca, veritabanı depolama alanından tasarruf etmek için eski olayları kaldırmak üzere bir cron işi (zamanlanmış işlev) oluşturmak isteyebilirsiniz. En azından bir yangın planına ihtiyacınız var ve biraz daha yapılandırma olabilir. Mesela her pazar saat 11'de çalıştırabilirsiniz. https://firebase.google.com/docs/functions/schedule-functions

Bu test edilmemiştir , ancak birkaç değişiklikle çalışmalıdır:

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

Ve de koleksiyonlarını korumak için son unutma firestore.rules :

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

Güncelleme: Sorgular

Sorgu sayılarını da otomatikleştirmek istiyorsanız diğer yanıtıma ekleyerek, bulut işlevinizde bu değiştirilmiş kodu kullanabilirsiniz:

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

Hangi userDocument içindeki postsCount değerini otomatik olarak güncelleyecektir . Bu şekilde birçok sayıya kolayca başka bir sayı ekleyebilirsiniz. Bu size bir şeyi nasıl otomatik hale getirebileceğiniz konusunda fikir verir. Ayrıca size etkinlikleri silmenin başka bir yolunu verdim. Silmek için her tarihi okumak zorundasınız, böylece daha sonra silmek için sizi gerçekten kurtarmaz, sadece işlevi yavaşlatır.

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

Ayrıca, evrensel işlevlerin her onWrite çağrı döneminde de çalışacağı konusunda sizi uyarmalıyım. İşlevi yalnızca özel koleksiyonlarınızın onCreate ve onDelete örneklerinde çalıştırmak daha ucuz olabilir. Kullandığımız noSQL veritabanı gibi, tekrarlanan kod ve veriler paradan tasarruf etmenizi sağlayabilir.


kolay erişim için ortam hakkında bir makale yazın.
ahmadalibaloch


0

Yukarıdaki cevaplardan bazılarına dayanarak bu çalışmayı elde etmek için biraz zamanımı aldım, bu yüzden başkalarının kullanması için paylaşacağımı düşündüm. Umarım faydalıdır.

'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

Farklı yaklaşımlarla çok şey denedim. Ve son olarak, yöntemlerden birini geliştiriyorum. Öncelikle ayrı bir koleksiyon oluşturmanız ve orada tüm etkinlikleri kaydetmeniz gerekir. İkincisi, zamanla tetiklenecek yeni bir lambda oluşturmanız gerekiyor. Bu lambda olay toplama ve açık olay belgelerindeki olayları sayar. Makalede kod ayrıntıları. https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca


Lütfen yanıtın kendisine ilgili ayrıntıları ve kodu ekleyin, blog yayınlarınızdaki kişileri işaret etmek gerçekten StackOverflow'un noktası değildir.
DBS

0

Bu sorgu belge sayısıyla sonuçlanacaktır.

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

console.log(count)

1
Her seferinde koleksiyondaki tüm belgeleri alırken iyi bir çözüm değil. Çok pahalıya mal olacak. Daha iyi bir yaklaşım, bu koleksiyona her yeni belge eklendiğinde bir sayaç oluşturmaktır, böylece birkaç bin yerine bir belgeyi getirebilirsiniz.
Corentin Houdayer

0

Bu, sayısal benzersiz kimlik oluşturmak için saymayı kullanır. Kullanımım sırasında , kimliğin gerekli olduğu silinmiş olsa bile, hiç azalmayacağımdocument .

collectionBenzersiz sayısal değer gerektiren bir oluşturma üzerine

  1. Bir koleksiyon belirleyin appData, tek belgeyle setbirlikte .docidonly
  2. Ekranda uniqueNumericIDAmount0 olarak ayarlayınfirebase firestore console
  3. doc.data().uniqueNumericIDAmount + 1Benzersiz sayısal kimlik olarak kullan
  4. appDataKoleksiyonu şu uniqueNumericIDAmountşekilde güncelleyin: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
Bu yanıt, kod örneğiyle ilgili daha fazla bağlam kullanabilir.
ShellNinja
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.