Jumlah koleksi Cloud Firestore

Jawaban:

189

Pembaruan (April 2019) - FieldValue.increment (lihat solusi koleksi besar)


Seperti banyak pertanyaan, jawabannya adalah - Itu tergantung .

Anda harus sangat berhati-hati saat menangani sejumlah besar data di ujung depan. Selain membuat ujung depan Anda terasa lamban, Firestore juga menagih Anda $ 0,60 per juta bacaan yang Anda buat.


Koleksi kecil (kurang dari 100 dokumen)

Gunakan dengan hati-hati - Pengalaman pengguna Frontend mungkin terpukul

Menangani ini di ujung depan harus baik-baik saja selama Anda tidak melakukan terlalu banyak logika dengan array yang dikembalikan ini.

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

Koleksi sedang (100 hingga 1000 dokumen)

Gunakan dengan hati-hati - doa baca Firestore mungkin sangat mahal

Menangani ini di ujung depan tidak layak karena memiliki terlalu banyak potensi untuk memperlambat sistem pengguna. Kita harus menangani sisi server logika ini dan hanya mengembalikan ukurannya.

Kelemahan dari metode ini adalah Anda masih menggunakan bacaan firestore (sama dengan ukuran koleksi Anda), yang dalam jangka panjang mungkin berakhir dengan biaya lebih dari yang diharapkan.

Fungsi cloud:

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

Paling depan:

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

Koleksi besar (1000+ dokumen)

Solusi yang paling scalable


FieldValue.increment ()

Pada April 2019 Firestore sekarang memungkinkan penghitung yang bertambah, sepenuhnya atom, dan tanpa membaca data sebelumnya . Ini memastikan kami memiliki nilai penghitung yang benar bahkan ketika memperbarui dari berbagai sumber secara bersamaan (sebelumnya diselesaikan dengan menggunakan transaksi), sambil juga mengurangi jumlah bacaan basis data yang kami lakukan.


Dengan mendengarkan setiap dokumen yang dihapus atau dibuat, kami dapat menambah atau menghapus dari bidang jumlah yang ada di database.

Lihat dokumen firestore - Penghitung Terdistribusi Atau lihat Agregasi Data oleh Jeff Delaney. Panduannya benar-benar fantastis bagi siapa saja yang menggunakan AngularFire, tetapi pelajarannya harus dibawa ke kerangka kerja lain juga.

Fungsi cloud:

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

Sekarang di frontend Anda bisa menanyakan bidang numberOfDocs ini untuk mendapatkan ukuran koleksi.

Matthew Mullin
sumber
17
Solusi hebat untuk koleksi besar! Saya hanya ingin menambahkan bahwa pelaksana harus membungkus baca dan tulis dalam sebuah firestore.runTransaction { ... }blok. Ini memperbaiki masalah konkurensi dengan mengakses numberOfDocs.
efemoney
2
Metode ini menggunakan penghitungan ulang jumlah catatan. Jika Anda menggunakan penghitung dan menambah penghitung menggunakan transaksi, apakah itu tidak akan mencapai hasil yang sama tanpa biaya tambahan dan kebutuhan fungsi cloud?
user3836415
10
Solusi untuk koleksi besar tidak idempoten dan tidak berfungsi pada skala apa pun. Pemicu dokumen Firestore dijamin berjalan setidaknya sekali, tetapi dapat berjalan beberapa kali. Ketika ini terjadi, bahkan mempertahankan pembaruan di dalam suatu transaksi dapat berjalan lebih dari satu kali, yang akan memberi Anda nomor palsu. Ketika saya mencoba ini, saya mengalami masalah dengan kurang dari selusin kreasi dokumen sekaligus.
Tym Pollack
2
Hai @TymPollack. Saya perhatikan beberapa perilaku tidak konsisten menggunakan pemicu cloud. Adakah peluang Anda dapat menautkan saya ke dan artikel atau forum untuk menjelaskan perilaku yang Anda alami?
Matius Mullin
2
@ cmprogram Anda membaca seluruh koleksi dan data saat menggunakan db.collection ('...') ... jadi ketika Anda tidak membutuhkan data maka Anda benar - Anda dapat dengan mudah meminta daftar ID pengumpulan (bukan data dokumen koleksi) dan dihitung sebagai satu baca.
atereshkov
24

Cara paling sederhana untuk melakukannya adalah dengan membaca ukuran "querySnapshot".

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

Anda juga dapat membaca panjang array dokumen di dalam "querySnapshot".

querySnapshot.docs.length;

Atau jika "querySnapshot" kosong dengan membaca nilai kosong, yang akan mengembalikan nilai boolean.

querySnapshot.empty;
Ompel
sumber
73
Ketahuilah bahwa setiap dokumen "menelan biaya" satu kali dibaca. Jadi jika Anda menghitung 100 item dalam koleksi dengan cara ini, Anda akan dikenakan biaya untuk 100 kali dibaca!
Georg
Benar, tetapi tidak ada cara lain untuk meringkas jumlah dokumen dalam koleksi. Dan jika Anda sudah mengambil koleksi, membaca array "dokumen" tidak akan memerlukan pengambilan lagi, maka tidak akan "biaya" lebih banyak bacaan.
Ompel
5
Ini membaca semua dokumen dalam memori! Semoga berhasil dengan dataset besar ...
Dan Dascalescu
84
ini benar-benar tidak dapat dipercaya yang tidak dimiliki Firebase Firestore db.collection.count(). Berpikir menjatuhkan mereka hanya untuk ini
Blue Bot
8
Khusus untuk koleksi besar, tidak adil menagih kepada kami seolah kami benar-benar mengunduh dan menggunakan semua dokumen. Hitung untuk tabel (koleksi) adalah fitur dasar. Mempertimbangkan model penetapan harga mereka dan Firestore diluncurkan pada 2017, sungguh luar biasa bahwa Google tidak menyediakan cara alternatif untuk mendapatkan ukuran koleksi. Sampai mereka tidak mengimplementasikannya, mereka setidaknya harus menghindari mengenakan biaya untuk itu.
nibbana
23

Sejauh yang saya tahu tidak ada solusi built-in untuk ini dan hanya mungkin dalam node SDK sekarang. Jika Anda punya

db.collection('someCollection')

kamu bisa memakai

.select([fields])

untuk menentukan bidang mana yang ingin Anda pilih. Jika Anda melakukan pilih kosong () Anda hanya akan mendapatkan berbagai referensi dokumen.

contoh:

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

Solusi ini hanya optimasi untuk kasus terburuk mengunduh semua dokumen dan tidak menskala koleksi besar!

Lihat juga ini:
Cara mendapatkan jumlah dokumen dalam koleksi dengan Cloud Firestore

jbb
sumber
Dalam pengalaman saya, select(['_id'])lebih cepat dariselect()
JAnton
13

Hati-hati menghitung jumlah dokumen untuk koleksi besar . Ini sedikit rumit dengan basis data firestore jika Anda ingin memiliki penghitung yang telah dihitung untuk setiap koleksi.

Kode seperti ini tidak berfungsi dalam hal ini:

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

Alasannya adalah karena setiap pemicu cloud firestore harus idempoten, seperti yang dikatakan dokumentasi firestore: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guaraminan

Larutan

Jadi, untuk mencegah beberapa eksekusi kode Anda, Anda perlu mengelola dengan peristiwa dan transaksi. Ini adalah cara khusus saya untuk menangani penghitung koleksi besar:

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

Gunakan kasing di sini:

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

Seperti yang Anda lihat, kunci untuk mencegah beberapa eksekusi adalah properti yang disebut eventId di objek konteks. Jika fungsi telah ditangani berkali-kali untuk acara yang sama, id acara akan sama dalam semua kasus. Sayangnya, Anda harus memiliki koleksi "acara" di database Anda.

Ferran Verdés
sumber
2
Mereka mengatakan ini seolah-olah perilaku ini akan diperbaiki dalam rilis 1.0. Fungsi Amazon AWS mengalami masalah yang sama. Sesuatu yang begitu sederhana seperti menghitung bidang menjadi rumit, dan mahal.
MarcG
Akan mencoba ini sekarang karena sepertinya solusi yang lebih baik. Apakah Anda pernah kembali dan membersihkan koleksi acara Anda? Saya berpikir untuk hanya menambahkan bidang tanggal dan membersihkan lebih dari satu hari atau sesuatu untuk menjaga data tetap kecil (mungkin 1 mil + acara / hari). Kecuali ada cara mudah di FS untuk melakukan itu ... hanya menggunakan FS beberapa bulan.
Tym Pollack
1
Bisakah kita memverifikasi bahwa context.eventIdakan selalu sama pada banyak pemanggilan dengan pemicu yang sama? Dalam pengujian saya tampaknya konsisten, tetapi saya tidak dapat menemukan dokumentasi "resmi" yang menyatakan ini.
Mike McLin
2
Jadi setelah menggunakan ini sebentar, saya telah menemukan bahwa, sementara solusi ini tidak bekerja dengan tepat satu penulisan, yang bagus, jika terlalu banyak memicu api dari banyak dokumen yang sedang ditulis sekaligus dan mencoba untuk memperbarui jumlah dokumen yang sama, Anda dapat dapatkan kesalahan pertikaian dari firestore. Pernahkah Anda menemukan itu, dan bagaimana Anda mengatasinya? (Kesalahan: 10 DIBAYARKAN: Terlalu banyak pertentangan pada dokumen-dokumen ini. Silakan coba lagi.)
Tym Pollack
1
Lihat @TymPollack di counter didistribusikan dokumen menulis terbatas pada kira-kira satu update per detik
Jamie
8

Pada tahun 2020 ini masih belum tersedia di Firebase SDK namun tersedia di Firebase Extensions (Beta) namun cukup rumit untuk menyiapkan dan menggunakan ...

Pendekatan yang masuk akal

Pembantu ... (membuat / menghapus sepertinya berlebihan tetapi lebih murah daripada saat diperbarui)

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

Kait Firestore yang Diekspor


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

Beraksi

Kumpulan akar bangunan dan semua koleksi sub akan dilacak.

masukkan deskripsi gambar di sini

Di sini di bawah /counters/jalur root

masukkan deskripsi gambar di sini

Sekarang jumlah koleksi akan diperbarui secara otomatis dan akhirnya! Jika Anda membutuhkan hitungan, cukup gunakan jalur pengumpulan dan awali dengan counters.

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
  .doc('counters/' + collectionPath)
  .get()
  .then(snap => snap.get('count'));
Ben Winding
sumber
Bukankah ini mengalami batasan yang sama "1 pembaruan dokumen per detik"?
Ayyappa
Ya, tetapi pada akhirnya konsisten, artinya jumlah pengumpulan pada akhirnya akan sejalan dengan jumlah pengumpulan yang sebenarnya, itu adalah solusi termudah untuk diterapkan dan dalam banyak kasus keterlambatan singkat dalam jumlah dapat diterima.
Ben Winding
7

Saya setuju dengan @Matthew, biayanya akan banyak jika Anda melakukan permintaan seperti itu.

[SARAN UNTUK PEMBANGUN SEBELUM MEMULAI PROYEKNYA]

Karena kami telah meramalkan situasi ini di awal, kami sebenarnya dapat membuat koleksi yaitu penghitung dengan dokumen untuk menyimpan semua penghitung di bidang dengan tipe number.

Sebagai contoh:

Untuk setiap operasi CRUD pada koleksi, perbarui dokumen counter:

  1. Saat Anda membuat koleksi / subkoleksi baru: (+1 di konter) [1 operasi tulis]
  2. Saat Anda menghapus koleksi / subkoleksi: (-1 di konter) [1 operasi tulis]
  3. Saat Anda memperbarui koleksi / subkoleksi yang ada, jangan lakukan apa pun pada dokumen penghitung: (0)
  4. Saat Anda membaca koleksi / subkoleksi yang ada, jangan lakukan apa pun pada dokumen penghitung: (0)

Lain kali, ketika Anda ingin mendapatkan jumlah koleksi, Anda hanya perlu query / arahkan ke bidang dokumen. [1 operasi baca]

Selain itu, Anda dapat menyimpan nama koleksi dalam array, tetapi ini akan sulit, kondisi array di firebase ditampilkan seperti di bawah ini:

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

Jadi, jika Anda tidak akan menghapus koleksi, Anda sebenarnya dapat menggunakan array untuk menyimpan daftar nama koleksi alih-alih menanyakan semua koleksi setiap waktu.

Semoga ini bisa membantu!

Angus Tay
sumber
Untuk koleksi kecil, mungkin. Tetapi perlu diingat batas ukuran dokumen Firestore adalah ~ 1MB, yang, jika ID dokumen dalam koleksi dibuat secara otomatis (20 byte), maka Anda hanya akan dapat menyimpan ~ 52.425 dari mereka sebelum doc memegang array terlalu besar. Saya kira sebagai solusi untuk itu Anda bisa membuat dokumen baru setiap 50.000 elemen, tetapi mempertahankan array itu akan sepenuhnya tidak dapat dikelola. Lebih lanjut, ketika ukuran dokumen bertambah, perlu waktu lebih lama untuk membaca dan memperbarui, yang pada akhirnya akan membuat operasi lain pada waktu habis dalam pertentangan.
Tym Pollack
5

Tidak, tidak ada dukungan bawaan untuk permintaan agregasi sekarang. Namun ada beberapa hal yang bisa Anda lakukan.

Yang pertama didokumentasikan di sini . Anda dapat menggunakan transaksi atau fungsi cloud untuk mempertahankan informasi agregat:

Contoh ini menunjukkan cara menggunakan fungsi untuk melacak jumlah peringkat dalam subkoleksi, serta peringkat rata-rata.

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

Solusi yang disebutkan jbb juga berguna jika Anda hanya ingin jarang menghitung dokumen. Pastikan untuk menggunakan select()pernyataan untuk menghindari mengunduh semua dokumen (itu banyak bandwidth ketika Anda hanya perlu menghitung). select()hanya tersedia di SDK server untuk saat ini sehingga solusi tidak akan berfungsi di aplikasi seluler.

Sam Stern
sumber
1
Solusi ini tidak idempoten, jadi pemicu apa pun yang menyala lebih dari sekali akan membuang jumlah peringkat dan rata-rata Anda.
Tym Pollack
4

Tidak ada opsi langsung yang tersedia. Anda tidak bisa melakukannya db.collection("CollectionName").count(). Di bawah ini adalah dua cara dimana Anda dapat menemukan hitungan jumlah dokumen dalam koleksi.

1: - Dapatkan semua dokumen dalam koleksi dan kemudian dapatkan ukurannya. (Bukan Solusi terbaik)

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

Dengan menggunakan kode di atas, bacaan dokumen Anda akan sama dengan ukuran dokumen dalam koleksi dan itulah alasan mengapa seseorang harus menghindari menggunakan solusi di atas.

2: - Buat dokumen terpisah dengan koleksi Anda yang akan menyimpan jumlah dokumen dalam koleksi. (Solusi Terbaik)

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

Di atas kami membuat dokumen dengan jumlah nama untuk menyimpan semua informasi jumlah. Anda dapat memperbarui dokumen jumlah dengan cara berikut: -

  • Buat pemicu firestore pada jumlah dokumen
  • Menambah properti hitung dokumen hitung saat dokumen baru dibuat.
  • Mengurangi properti hitung dokumen hitung ketika dokumen dihapus.

harga wrt (Document Read = 1) dan pengambilan data cepat solusi di atas adalah baik.

Nipun Madan
sumber
3

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

Dalam contoh ini kami menambah instanceCountbidang dalam proyek setiap kali dokumen ditambahkan ke instancessub koleksi. Jika bidang belum ada, maka akan dibuat dan ditambahkan ke 1.

Peningkatan ini bersifat transaksional secara internal tetapi Anda harus menggunakan penghitung terdistribusi jika Anda perlu menambah lebih sering daripada setiap 1 detik.

Ini sering lebih baik untuk diterapkan onCreatedan onDeletedaripada onWriteAnda akan meminta onWritepembaruan yang berarti Anda menghabiskan lebih banyak uang untuk pemanggilan fungsi yang tidak perlu (jika Anda memperbarui dokumen dalam koleksi Anda).

Dominic
sumber
2

Solusi adalah untuk:

tulis penghitung di dokumen firebase, yang Anda tambahkan dalam transaksi setiap kali Anda membuat entri baru

Anda menyimpan hitungan di bidang entri baru Anda (yaitu: posisi: 4).

Kemudian Anda membuat indeks pada bidang itu (posisi DESC).

Anda dapat melakukan melewati batas + dengan kueri. Di mana ("posisi", "<" x) .OrderBy ("posisi", DESC)

Semoga ini membantu!

Kathan Shah
sumber
1

Saya membuat fungsi universal menggunakan semua ide ini untuk menangani semua situasi tandingan (kecuali kueri).

Satu-satunya pengecualian adalah ketika melakukan begitu banyak menulis sedetik, itu memperlambat Anda. Contohnya adalah suka pada pos yang sedang tren. Sebagai contoh, itu berlebihan pada posting blog, dan akan dikenakan biaya lebih banyak. Saya sarankan membuat fungsi terpisah dalam hal itu menggunakan pecahan: 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);
        });;
    }
}

Ini menangani acara, kenaikan, dan transaksi. Keindahan dalam ini, adalah bahwa jika Anda tidak yakin tentang keakuratan dokumen (mungkin saat masih dalam versi beta), Anda dapat menghapus penghitung agar secara otomatis menambahkannya pada pemicu berikutnya. Ya, ini biayanya, jadi jangan hapus kalau tidak.

Hal yang sama untuk mendapatkan hitungan:

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

Selain itu, Anda mungkin ingin membuat pekerjaan cron (fungsi terjadwal) untuk menghapus acara lama untuk menghemat uang pada penyimpanan basis data. Anda memerlukan setidaknya paket blaze, dan mungkin ada beberapa konfigurasi lagi. Anda bisa menjalankannya setiap minggu pukul 11 ​​malam, misalnya. https://firebase.google.com/docs/functions/schedule-functions

Ini belum diuji , tetapi harus bekerja dengan beberapa penyesuaian:

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

Dan terakhir, jangan lupa untuk melindungi koleksi di firestore.rules :

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

Perbarui: Permintaan

Menambahkan ke jawaban saya yang lain jika Anda ingin mengotomatiskan jumlah kueri juga, Anda dapat menggunakan kode yang dimodifikasi ini di fungsi cloud Anda:

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

Yang secara otomatis akan memperbarui postsCount di userDocument. Anda dapat dengan mudah menambahkan yang lain ke banyak hitungan dengan cara ini. Ini hanya memberi Anda ide tentang bagaimana Anda dapat mengotomatisasi sesuatu. Saya juga memberi Anda cara lain untuk menghapus acara. Anda harus membaca setiap tanggal untuk menghapusnya, jadi itu tidak benar-benar menyelamatkan Anda untuk menghapusnya nanti, hanya membuat fungsinya lebih lambat.

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

Saya juga harus memperingatkan Anda bahwa fungsi universal akan berjalan pada setiap periode panggilan onWrite. Mungkin lebih murah untuk hanya menjalankan fungsi di onCreate dan pada contoh onDelete dari koleksi spesifik Anda. Seperti database noSQL yang kami gunakan, kode dan data yang diulang dapat menghemat uang Anda.

Jonathan
sumber
tulis artikel tentang itu di media agar mudah diakses.
ahmadalibaloch
0

Butuh waktu beberapa saat untuk menyelesaikannya berdasarkan beberapa jawaban di atas, jadi saya pikir saya akan membagikannya untuk digunakan orang lain. Semoga bermanfaat.

'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;
});
Rob Phillips
sumber
0

Saya sudah mencoba banyak dengan pendekatan yang berbeda. Dan akhirnya, saya meningkatkan salah satu metode. Pertama, Anda perlu membuat koleksi terpisah dan menyimpan semua acara di sana. Kedua, Anda perlu membuat lambda baru untuk dipicu oleh waktu. Lambda ini akan Menghitung acara dalam koleksi acara dan menghapus dokumen acara. Rincian kode dalam artikel. https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca

Ihor Malaniuk
sumber
Harap sertakan detail dan kode yang relevan dalam jawaban itu sendiri , menunjuk orang di posting blog Anda sebenarnya bukan poin dari StackOverflow.
DBS
0

Kueri ini akan menghasilkan jumlah dokumen.

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

console.log(count)
Aravin
sumber
1
Bukan solusi yang baik karena Anda mengambil semua dokumen dari koleksi setiap saat. Itu akan menghabiskan banyak biaya. Pendekatan yang lebih baik adalah dengan menyiapkan penghitung setiap kali dokumen baru ditambahkan dalam koleksi ini sehingga Anda dapat mengambil satu dokumen, bukan beberapa ribu.
Corentin Houdayer
0

Ini menggunakan penghitungan untuk membuat ID unik numerik. Dalam penggunaan saya, saya tidak akan pernah mengurangi , bahkan ketika documentID diperlukan untuk dihapus.

Setelah collectionpenciptaan yang membutuhkan nilai numerik yang unik

  1. Tentukan koleksi appDatadengan satu dokumen, setdengan .docidonly
  2. Set uniqueNumericIDAmountke 0 difirebase firestore console
  3. Gunakan doc.data().uniqueNumericIDAmount + 1sebagai id angka unik
  4. Perbarui appDatakoleksi uniqueNumericIDAmountdenganfirebase.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);
            });
    });
Nick Carducci
sumber
-1
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            int Counter = documentSnapshots.size();

        }
    });
Sampath Patro
sumber
1
Jawaban ini dapat menggunakan lebih banyak konteks untuk contoh kode.
ShellNinja