การสะสมของ Cloud Firestore


149

เป็นไปได้หรือไม่ที่จะนับว่ามีกี่รายการที่คอลเลกชันใช้ฐานข้อมูล Firebase ใหม่คือ Cloud Firestore

ถ้าเป็นเช่นนั้นฉันจะทำอย่างไร


คำตอบ:


190

อัปเดต (เมษายน 2019) - FieldValue.increment (ดูโซลูชันการรวบรวมขนาดใหญ่)


เช่นเดียวกับหลาย ๆ คำถามคำตอบคือ - มันขึ้นอยู่กับ

คุณควรระมัดระวังในการจัดการข้อมูลจำนวนมากที่ส่วนหน้า ด้านบนของการทำให้ส่วนหน้าของคุณรู้สึกซบเซาFirestore ยังคิดค่าใช้จ่าย $ 0.60 ต่อการอ่านล้านครั้งที่คุณทำ


ชุดเล็ก (น้อยกว่า 100 เอกสาร)

ใช้ด้วยความระมัดระวัง - ประสบการณ์ผู้ใช้ส่วนหน้าอาจได้รับความนิยม

การจัดการสิ่งนี้ในส่วนหน้าควรจะดีถ้าคุณไม่ได้ใช้ตรรกะมากเกินไปกับอาเรย์ที่ส่งคืน

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

การรวบรวมขนาดกลาง (100 ถึง 1,000 เอกสาร)

ใช้ด้วยความระมัดระวัง - Firestore การร้องขอการอ่านอาจมีค่าใช้จ่ายสูง

การจัดการสิ่งนี้ในส่วนหน้านั้นไม่สามารถทำได้เนื่องจากมีศักยภาพมากเกินไปที่จะทำให้ระบบของผู้ใช้ช้าลง เราควรจัดการด้านเซิร์ฟเวอร์เซิร์ฟเวอร์นี้และคืนขนาดเท่านั้น

ข้อเสียของวิธีนี้คือคุณยังคงเรียกใช้การอ่าน firestore (เท่ากับขนาดของคอลเลกชันของคุณ) ซึ่งในระยะยาวอาจทำให้คุณต้องเสียค่าใช้จ่ายมากกว่าที่คาดไว้

ฟังก์ชั่นคลาวด์:

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

ส่วนหน้า:

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

ชุดใหญ่ (1,000+ เอกสาร)

โซลูชันที่ปรับขนาดได้มากที่สุด


FieldValue.increment ()

ณ เมษายน 2019 FireStore ขณะนี้ช่วยให้การเพิ่มเคาน์เตอร์สมบูรณ์อะตอมและโดยไม่ต้องอ่านข้อมูลก่อน สิ่งนี้ช่วยให้มั่นใจว่าเรามีค่าตัวนับที่ถูกต้องแม้ว่าจะอัปเดตจากหลาย ๆ แหล่งพร้อมกัน (แก้ไขก่อนหน้านี้โดยใช้ธุรกรรม) ในขณะที่ยังลดจำนวนของฐานข้อมูลที่เราอ่าน


โดยการฟังเอกสารใด ๆ ที่ลบหรือสร้างเราสามารถเพิ่มหรือลบออกจากเขตข้อมูลนับที่อยู่ในฐานข้อมูล

ดูเอกสาร firestore - เคาน์เตอร์แบบกระจาย หรือดูที่Data Aggregationโดย Jeff Delaney ไกด์ของเขานั้นยอดเยี่ยมอย่างแท้จริงสำหรับทุกคนที่ใช้ AngularFire แต่บทเรียนของเขาควรนำไปใช้กับกรอบงานอื่น ๆ เช่นกัน

ฟังก์ชั่นคลาวด์:

export const documentWriteListener = 
    functions.firestore.document('collection/{documentUid}')
    .onWrite((change, context) => {

    if (!change.before.exists) {
        // New document Created : add one to count

        db.doc(docRef).update({numberOfDocs: FieldValue.increment(1)});

    } else if (change.before.exists && change.after.exists) {
        // Updating existing document : Do nothing

    } else if (!change.after.exists) {
        // Deleting document : subtract one from count

        db.doc(docRef).update({numberOfDocs: FieldValue.increment(-1)});

    }

return;
});

ในส่วนหน้าคุณสามารถสอบถามฟิลด์ numberOfDocs นี้เพื่อรับขนาดของคอลเลกชัน


17
ทางออกที่ดีสำหรับคอลเลกชันขนาดใหญ่! ฉันแค่ต้องการเพิ่มที่ผู้ใช้งานควรตัดการอ่านและเขียนในfirestore.runTransaction { ... }บล็อก numberOfDocsนี้ประเด็นการแก้ไขเห็นพ้องด้วยการเข้าถึง
efemoney

2
วิธีการเหล่านี้ใช้จำนวนบันทึกใหม่ หากคุณใช้ตัวนับและเพิ่มตัวนับโดยใช้การทำธุรกรรมนั่นจะไม่ได้ผลเช่นเดียวกันหากไม่มีค่าใช้จ่ายเพิ่มเติมและความต้องการฟังก์ชั่นคลาวด์หรือไม่?
user3836415

10
วิธีแก้ปัญหาสำหรับคอลเลกชันขนาดใหญ่ไม่ใช่ idempotent และไม่สามารถทำงานได้ในทุกขนาด ทริกเกอร์เอกสาร Firestore รับประกันว่าจะทำงานอย่างน้อยหนึ่งครั้ง แต่อาจเรียกใช้หลายครั้ง เมื่อสิ่งนี้เกิดขึ้นแม้กระทั่งการรักษาการอัปเดตภายในธุรกรรมสามารถเรียกใช้มากกว่าหนึ่งครั้งได้ เมื่อฉันลองทำสิ่งนี้ฉันพบปัญหาในการสร้างเอกสารน้อยกว่าหนึ่งโหลต่อครั้ง
Tym Pollack

2
สวัสดี @TymPollack ฉันสังเกตเห็นพฤติกรรมที่ไม่สอดคล้องกันบางอย่างโดยใช้ทริกเกอร์ของคลาวด์ โอกาสใดก็ตามที่คุณสามารถเชื่อมโยงฉันกับบทความหรือฟอรัมเพื่ออธิบายพฤติกรรมที่คุณเคยประสบ
Matthew Mullin

2
@cmprogram คุณกำลังอ่านคอลเลกชันและข้อมูลทั้งหมดเมื่อใช้ db.collection ('... ') ... ดังนั้นเมื่อคุณไม่ต้องการข้อมูลคุณก็จะถูกต้อง - คุณสามารถขอรายการ ID การรวบรวม (ไม่ใช่ข้อมูลเอกสารการรวบรวม) และนับเป็นหนึ่งการอ่าน
atereshkov

25

วิธีที่ง่ายที่สุดในการทำเช่นนั้นคือการอ่านขนาดของ "querySnapshot"

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

นอกจากนี้คุณยังสามารถอ่านความยาวของอาร์เรย์เอกสารภายใน "querySnapshot"

querySnapshot.docs.length;

หรือถ้า "querySnapshot" ว่างเปล่าโดยการอ่านค่าว่างซึ่งจะส่งกลับค่าบูลีน

querySnapshot.empty;

73
โปรดทราบว่าแต่ละเอกสาร "ค่าใช้จ่าย" หนึ่งฉบับอ่านแล้ว ดังนั้นหากคุณนับ 100 รายการในชุดสะสมด้วยวิธีนี้คุณจะถูกเรียกเก็บเงิน 100 ครั้งอ่าน!
เฟรดริก

ถูกต้อง แต่ไม่มีวิธีอื่นในการสรุปจำนวนเอกสารในการรวบรวม และถ้าคุณดึงคอลเลคชั่นไปแล้วการอ่านอาร์เรย์ "เอกสาร" ไม่จำเป็นต้องดึงข้อมูลอีกต่อไปดังนั้นการอ่านเพิ่มเติมจะไม่ "เสียค่าใช้จ่าย"
Ompel

5
สิ่งนี้จะอ่านเอกสารทั้งหมดในหน่วยความจำ! ขอให้โชคดีกับชุดข้อมูลขนาดใหญ่ ...
Dan Dascalescu

85
นี้จะไม่น่าเชื่อจริงๆว่า Firebase FireStore db.collection.count()ไม่ได้มีชนิดของ กำลังคิดที่จะทิ้งพวกเขาไว้เพื่อสิ่งนี้
Blue Bot

8
โดยเฉพาะอย่างยิ่งสำหรับคอลเลกชันขนาดใหญ่มันไม่ยุติธรรมที่จะเรียกเก็บเงินจากเราราวกับว่าเราดาวน์โหลดและใช้เอกสารทั้งหมดจริง นับสำหรับตาราง (คอลเลกชัน) เป็นคุณสมบัติพื้นฐานดังกล่าว เมื่อพิจารณาถึงรูปแบบการกำหนดราคาและ Firestore เปิดตัวในปี 2560 เป็นเรื่องที่น่าเหลือเชื่อที่ Google ไม่มีทางเลือกอื่นในการเพิ่มขนาดของชุดสะสม อย่างน้อยพวกเขาก็ควรหลีกเลี่ยงการเรียกเก็บเงิน
นิพพาน

23

เท่าที่ฉันรู้ว่าไม่มีวิธีแก้ปัญหาแบบบิวด์อินและเป็นไปได้เฉพาะในโหนด sdk เท่านั้น หากคุณมี

db.collection('someCollection')

คุณสามารถใช้ได้

.select([fields])

เพื่อกำหนดฟิลด์ที่คุณต้องการเลือก หากคุณเลือกว่างเปล่า () คุณจะได้รับเอกสารอ้างอิงหลายชุด

ตัวอย่าง:

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

โซลูชันนี้เป็นเพียงการปรับให้เหมาะสมที่สุดสำหรับกรณีที่แย่ที่สุดในการดาวน์โหลดเอกสารทั้งหมดและไม่ได้เพิ่มจำนวนมากขึ้น!

ดูที่นี่:
วิธีรับจำนวนเอกสารในคอลเลกชันด้วย Cloud Firestore


จากประสบการณ์ของฉันselect(['_id'])เร็วกว่าselect()
JAnton

13

นับเป็นตัวเลขที่ระมัดระวังของเอกสารสำหรับคอลเลกชันขนาดใหญ่ มันซับซ้อนเล็กน้อยกับฐานข้อมูล firestore ถ้าคุณต้องการมีตัวนับที่คำนวณล่วงหน้าสำหรับทุกคอลเลกชัน

รหัสเช่นนี้ใช้ไม่ได้ในกรณีนี้:

export const customerCounterListener = 
    functions.firestore.document('customers/{customerId}')
    .onWrite((change, context) => {

    // on create
    if (!change.before.exists && change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count + 1
                     }))
    // on delete
    } else if (change.before.exists && !change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count - 1
                     }))
    }

    return null;
});

เหตุผลก็เพราะว่าทริกเกอร์ไฟร์เมฆทุกตัวต้องเป็น idempotent ตามที่เอกสารไฟเตอร์บอกว่า: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees

สารละลาย

ดังนั้นเพื่อป้องกันการประมวลผลรหัสของคุณหลายครั้งคุณต้องจัดการกับเหตุการณ์และธุรกรรม นี่เป็นวิธีเฉพาะของฉันในการจัดการกับเคาน์เตอร์สะสมขนาดใหญ่:

const executeOnce = (change, context, task) => {
    const eventRef = firestore.collection('events').doc(context.eventId);

    return firestore.runTransaction(t =>
        t
         .get(eventRef)
         .then(docSnap => (docSnap.exists ? null : task(t)))
         .then(() => t.set(eventRef, { processed: true }))
    );
};

const documentCounter = collectionName => (change, context) =>
    executeOnce(change, context, t => {
        // on create
        if (!change.before.exists && change.after.exists) {
            return t
                    .get(firestore.collection('metadatas')
                    .doc(collectionName))
                    .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: ((docSnap.data() && docSnap.data().count) || 0) + 1
                        }));
        // on delete
        } else if (change.before.exists && !change.after.exists) {
            return t
                     .get(firestore.collection('metadatas')
                     .doc(collectionName))
                     .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: docSnap.data().count - 1
                        }));
        }

        return null;
    });

ใช้กรณีที่นี่:

/**
 * Count documents in articles collection.
 */
exports.articlesCounter = functions.firestore
    .document('articles/{id}')
    .onWrite(documentCounter('articles'));

/**
 * Count documents in customers collection.
 */
exports.customersCounter = functions.firestore
    .document('customers/{id}')
    .onWrite(documentCounter('customers'));

อย่างที่คุณสามารถเห็นได้กุญแจสำคัญในการป้องกันการดำเนินการหลายอย่างคือคุณสมบัติที่เรียกว่าeventIdในวัตถุบริบท หากฟังก์ชั่นได้รับการจัดการหลายครั้งสำหรับเหตุการณ์เดียวกัน id เหตุการณ์จะเหมือนกันในทุกกรณี น่าเสียดายที่คุณต้องมีการรวบรวม "กิจกรรม" ในฐานข้อมูลของคุณ


2
พวกเขากำลังใช้ถ้อยคำนี้ราวกับว่าพฤติกรรมนี้จะได้รับการแก้ไขในรุ่น 1.0 ฟังก์ชั่น Amazon AWS ประสบปัญหาเดียวกัน บางสิ่งที่ง่ายมากเช่นการนับฟิลด์จะซับซ้อนและมีราคาแพง
MarcG

จะลองตอนนี้เพราะมันดูเหมือนเป็นทางออกที่ดีกว่า คุณย้อนกลับไปและกำจัดคอลเลกชันกิจกรรมของคุณตลอดไปหรือไม่? ฉันกำลังคิดที่จะเพิ่มเขตข้อมูลวันที่และล้างข้อมูลเก่ากว่าวันหรือบางอย่างเพื่อให้ชุดข้อมูลมีขนาดเล็ก (อาจเป็น 1mil + เหตุการณ์ / วัน) เว้นแต่จะมีวิธีง่าย ๆ ใน FS ที่จะทำเช่นนั้น ... ใช้ FS เพียงไม่กี่เดือนเท่านั้น
Tym Pollack

1
เราสามารถตรวจสอบได้ว่าcontext.eventIdจะเหมือนกันในการเรียกหลายครั้งของทริกเกอร์เดียวกันหรือไม่ ในการทดสอบของฉันดูเหมือนว่าจะสอดคล้องกัน แต่ฉันไม่สามารถหาเอกสาร "ทางการ" ใด ๆ ที่ระบุสิ่งนี้
Mike McLin

2
ดังนั้นหลังจากใช้ไประยะหนึ่งฉันพบว่าในขณะที่วิธีนี้ใช้ได้กับการเขียนหนึ่งครั้งซึ่งยอดเยี่ยมมากหากมีการเรียกใช้เอกสารจำนวนมากที่ถูกเขียนพร้อมกันและพยายามอัปเดตเอกสารการนับเดียวกันคุณสามารถ รับข้อผิดพลาดการแข่งขันจาก firestore คุณเคยเจอสิ่งเหล่านี้และคุณได้รอบมันได้อย่างไร (ข้อผิดพลาด: 10 ถูกยกเลิก: มีการโต้แย้งในเอกสารเหล่านี้มากเกินไปโปรดลองอีกครั้ง)
Tym Pollack

1
@TymPollack ดูที่เอกสารเคาน์เตอร์แบบกระจายที่เขียนถูก จำกัด ไว้ที่ประมาณหนึ่งอัปเดตต่อวินาที
Jamie

8

ในปี 2020 นี้ยังไม่พร้อมใช้งานใน Firebase SDK แต่มีให้ในFirebase Extensions (Beta)แต่มันค่อนข้างซับซ้อนในการติดตั้งและใช้ ...

แนวทางที่เหมาะสม

ผู้ช่วยเหลือ ... (สร้าง / ลบดูเหมือนซ้ำซ้อน แต่ราคาถูกกว่า onUpdate)

export const onCreateCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(1);
  await statsDoc.set(countDoc, { merge: true });
};

export const onDeleteCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(-1);
  await statsDoc.set(countDoc, { merge: true });
};

export interface CounterPath {
  watch: string;
  name: string;
}

ส่งออก Firestore hooks


export const Counters: CounterPath[] = [
  {
    name: "count_buildings",
    watch: "buildings/{id2}"
  },
  {
    name: "count_buildings_subcollections",
    watch: "buildings/{id2}/{id3}/{id4}"
  }
];


Counters.forEach(item => {
  exports[item.name + '_create'] = functions.firestore
    .document(item.watch)
    .onCreate(onCreateCounter());

  exports[item.name + '_delete'] = functions.firestore
    .document(item.watch)
    .onDelete(onDeleteCounter());
});

ในการปฏิบัติ

คอลเล็กชันรูทการสร้างและคอลเล็กชันย่อยทั้งหมดจะถูกติดตาม

ป้อนคำอธิบายรูปภาพที่นี่

ที่นี่ภายใต้/counters/เส้นทางของราก

ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้จำนวนคอลเลกชันจะอัปเดตโดยอัตโนมัติและในที่สุด! countersหากคุณจำเป็นต้องนับเพียงแค่ใช้เส้นทางคอลเลกชันและคำนำหน้าด้วย

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

สิ่งนี้ไม่ได้อยู่ภายใต้ข้อ จำกัด "การอัปเดตเอกสาร 1 ครั้งต่อวินาที" เดียวกันหรือไม่
Ayyappa

ใช่ แต่ในที่สุดก็สอดคล้องกันซึ่งหมายถึงจำนวนการรวบรวมในที่สุดจะสอดคล้องกับจำนวนการรวบรวมจริงมันเป็นทางออกที่ง่ายที่สุดในการติดตั้งและในหลาย ๆ กรณีการยอมรับความล่าช้าสั้น ๆ เป็นที่ยอมรับ
Ben Winding

7

ฉันเห็นด้วยกับ @Matthew มันจะมีค่าใช้จ่ายมากถ้าคุณทำแบบสอบถามดังกล่าว

[คำแนะนำสำหรับนักพัฒนาก่อนเริ่มโครงการของพวกเขา]

numberเนื่องจากเราได้เล็งเห็นสถานการณ์เช่นนี้ที่จุดเริ่มต้นที่เราจริงสามารถทำให้คอลเลกชันคือเคาน์เตอร์ที่มีเอกสารในการจัดเก็บเคาน์เตอร์ทั้งหมดในเขตข้อมูลที่มีชนิด

ตัวอย่างเช่น:

สำหรับการดำเนินการ CRUD แต่ละครั้งในการรวบรวมให้อัพเดตเอกสารตัวนับ:

  1. เมื่อคุณสร้างคอลเลกชันใหม่ / คอลเลกชันย่อย: (+1 ในตัวนับ) [1 การดำเนินการเขียน]
  2. เมื่อคุณลบคอลเลกชัน / ชุดย่อย: (-1 ในตัวนับ) [1 การดำเนินการเขียน]
  3. เมื่อคุณอัปเดตคอลเลกชัน / คอลเลกชันย่อยที่มีอยู่ไม่ต้องทำอะไรเลยในเอกสารตัวนับ: (0)
  4. เมื่อคุณอ่านคอลเล็กชัน / การรวบรวมย่อยที่มีอยู่ไม่ต้องทำอะไรเลยในเอกสารตัวนับ: (0)

ครั้งต่อไปเมื่อคุณต้องการรับจำนวนคอลเลกชันคุณเพียงแค่ต้องค้นหา / ชี้ไปที่ช่องเอกสาร [การดำเนินการอ่าน 1]

นอกจากนี้คุณสามารถจัดเก็บชื่อคอลเลกชันในอาเรย์ แต่จะยุ่งยากเงื่อนไขของอาเรย์ในฐานข้อมูลที่แสดงด้านล่าง:

// we send this
['a', 'b', 'c', 'd', 'e']
// Firebase stores this
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}

// since the keys are numeric and sequential,
// if we query the data, we get this
['a', 'b', 'c', 'd', 'e']

// however, if we then delete a, b, and d,
// they are no longer mostly sequential, so
// we do not get back an array
{2: 'c', 4: 'e'}

ดังนั้นถ้าคุณจะไม่ลบคอลเลกชันคุณสามารถใช้อาร์เรย์เพื่อจัดเก็บรายชื่อคอลเลกชันแทนการสืบค้นคอลเลกชันทั้งหมดได้ทุกครั้ง

หวังว่ามันจะช่วย!


สำหรับชุดเล็กอาจจะ แต่โปรดจำไว้ว่าขนาดเอกสาร จำกัด ของ Firestore คือ ~ 1MB ซึ่งหากรหัสเอกสารในคอลเลกชันนั้นสร้างขึ้นอัตโนมัติ (20 ไบต์) คุณจะสามารถเก็บได้ ~ 52,425 ก่อนที่เอกสารจะจัดเก็บอาร์เรย์ ใหญ่เกินไป ฉันเดาว่าเป็นวิธีแก้ปัญหาที่คุณสามารถสร้างเอกสารใหม่ทุก ๆ 50,000 องค์ประกอบ แต่จากนั้นการบำรุงรักษาอาร์เรย์เหล่านั้นจะไม่สามารถจัดการได้ทั้งหมด นอกจากนี้เมื่อขนาดเอกสารเพิ่มขึ้นจะใช้เวลานานในการอ่านและอัปเดตซึ่งในที่สุดจะทำให้การดำเนินการอื่น ๆ ที่เกี่ยวกับการหมดเวลาอยู่ในการช่วงชิง
Tym Pollack

5

ไม่ขณะนี้ไม่มีการรองรับการรวบรวมข้อความค้นหาในตัว อย่างไรก็ตามมีบางสิ่งที่คุณสามารถทำได้

ที่แรกก็คือการบันทึกไว้ที่นี่ คุณสามารถใช้ธุรกรรมหรือฟังก์ชั่นคลาวด์เพื่อรักษาข้อมูลรวม:

ตัวอย่างนี้แสดงวิธีใช้ฟังก์ชันเพื่อติดตามจำนวนการจัดเรตในการจัดเก็บย่อยรวมถึงเรตติ้งเฉลี่ย

exports.aggregateRatings = firestore
  .document('restaurants/{restId}/ratings/{ratingId}')
  .onWrite(event => {
    // Get value of the newly added rating
    var ratingVal = event.data.get('rating');

    // Get a reference to the restaurant
    var restRef = db.collection('restaurants').document(event.params.restId);

    // Update aggregations in a transaction
    return db.transaction(transaction => {
      return transaction.get(restRef).then(restDoc => {
        // Compute new number of ratings
        var newNumRatings = restDoc.data('numRatings') + 1;

        // Compute new average rating
        var oldRatingTotal = restDoc.data('avgRating') * restDoc.data('numRatings');
        var newAvgRating = (oldRatingTotal + ratingVal) / newNumRatings;

        // Update restaurant info
        return transaction.update(restRef, {
          avgRating: newAvgRating,
          numRatings: newNumRatings
        });
      });
    });
});

โซลูชันที่ jbb ที่กล่าวถึงนั้นมีประโยชน์ถ้าคุณต้องการนับเอกสารไม่บ่อยนัก ตรวจสอบให้แน่ใจว่าใช้select()คำสั่งเพื่อหลีกเลี่ยงการดาวน์โหลดเอกสารทั้งหมด (ซึ่งมีแบนด์วิดท์จำนวนมากเมื่อคุณต้องการนับ) select()มีให้ใช้งานใน SDK เซิร์ฟเวอร์เท่านั้นในตอนนี้ดังนั้นโซลูชันจะไม่ทำงานในแอปมือถือ


1
โซลูชันนี้ไม่ใช่ idempotent ดังนั้นทริกเกอร์ใด ๆ ที่ทำงานมากกว่าหนึ่งครั้งจะทำให้คะแนนและค่าเฉลี่ยของคุณลดลง
Tym Pollack

4

ไม่มีตัวเลือกโดยตรง คุณทำไม่db.collection("CollectionName").count()ได้ ด้านล่างนี้เป็นสองวิธีที่คุณสามารถค้นหาจำนวนเอกสารภายในคอลเล็กชัน

1: - รับเอกสารทั้งหมดในคอลเลกชันแล้วรับขนาด (ไม่ใช่ทางออกที่ดีที่สุด)

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

ด้วยการใช้รหัสด้านบนเอกสารของคุณจะอ่านเท่ากับขนาดของเอกสารภายในคอลเลกชันและนั่นคือเหตุผลว่าทำไมต้องหลีกเลี่ยงการใช้วิธีการแก้ปัญหาข้างต้น

2: - สร้างเอกสารแยกต่างหากด้วยในคอลเลกชันของคุณซึ่งจะเก็บจำนวนเอกสารในคอลเลกชัน (ทางออกที่ดีที่สุด)

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

ด้านบนเราสร้างเอกสารที่มีการนับชื่อเพื่อเก็บข้อมูลการนับทั้งหมดคุณสามารถอัปเดตเอกสารการนับด้วยวิธีต่อไปนี้: -

  • สร้างทริกเกอร์ firestore บนการนับเอกสาร
  • เพิ่มคุณสมบัติการนับของเอกสารการนับเมื่อสร้างเอกสารใหม่
  • ลดคุณสมบัติการนับของเอกสารนับเมื่อเอกสารถูกลบ

ราคา wrt (Document Read = 1) และการดึงข้อมูลที่รวดเร็วทางออกข้างต้นนั้นดี


3

เพิ่มตัวนับโดยใช้admin.firestore.FieldValue.increment :

exports.onInstanceCreate = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onCreate((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(1),
    })
  );

exports.onInstanceDelete = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onDelete((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(-1),
    })
  );

ในตัวอย่างนี้เราเพิ่มinstanceCountเขตข้อมูลในโครงการทุกครั้งที่มีการเพิ่มเอกสารลงในinstancesคอลเลกชันย่อย ถ้าเขตข้อมูลยังไม่มีอยู่จะมีการสร้างและเพิ่มเป็น 1

การเพิ่มขึ้นเป็นธุรกรรมภายใน แต่คุณควรใช้ตัวนับแบบกระจายหากคุณต้องการเพิ่มความถี่บ่อยกว่าทุก 1 วินาที

บ่อยครั้งเป็นที่นิยมใช้มากกว่าonCreateและonDeleteมากกว่าonWriteที่คุณจะเรียกร้องonWriteให้มีการปรับปรุงซึ่งหมายความว่าคุณใช้เงินมากขึ้นในการเรียกใช้ฟังก์ชันที่ไม่จำเป็น (ถ้าคุณอัปเดตเอกสารในชุดสะสมของคุณ)


2

วิธีแก้ปัญหาคือ:

เขียนตัวนับใน doc ของ firebase ซึ่งคุณเพิ่มภายในธุรกรรมทุกครั้งที่คุณสร้างรายการใหม่

คุณเก็บจำนวนไว้ในฟิลด์ของรายการใหม่ของคุณ (เช่น: ตำแหน่ง: 4)

จากนั้นคุณสร้างดัชนีในฟิลด์นั้น (ตำแหน่ง DESC)

คุณสามารถข้ามขีด จำกัด + ด้วยแบบสอบถามได้ที่นี่ ("ตำแหน่ง", "<" x) .OrderBy ("ตำแหน่ง", DESC)

หวังว่านี่จะช่วยได้!


1

ฉันสร้างฟังก์ชั่นสากลโดยใช้แนวคิดทั้งหมดเหล่านี้เพื่อจัดการกับสถานการณ์ตอบโต้ทั้งหมด (ยกเว้นข้อความค้นหา)

ข้อยกเว้นเพียงอย่างเดียวคือเมื่อคุณเขียนหลายวินาทีมันทำให้คุณช้าลง ตัวอย่างจะชอบในโพสต์ที่มีแนวโน้ม มันเกินความจริงในโพสต์บล็อกและจะทำให้คุณเสียค่าใช้จ่ายมากขึ้น ฉันขอแนะนำให้สร้างฟังก์ชั่นแยกต่างหากในกรณีนี้โดยใช้เศษ: https://firebase.google.com/docs/firestore/solutions/counters

// trigger collections
exports.myFunction = functions.firestore
    .document('{colId}/{docId}')
    .onWrite(async (change: any, context: any) => {
        return runCounter(change, context);
    });

// trigger sub-collections
exports.mySubFunction = functions.firestore
    .document('{colId}/{docId}/{subColId}/{subDocId}')
    .onWrite(async (change: any, context: any) => {
        return runCounter(change, context);
    });

// add change the count
const runCounter = async function (change: any, context: any) {

    const col = context.params.colId;

    const eventsDoc = '_events';
    const countersDoc = '_counters';

    // ignore helper collections
    if (col.startsWith('_')) {
        return null;
    }
    // simplify event types
    const createDoc = change.after.exists && !change.before.exists;
    const updateDoc = change.before.exists && change.after.exists;

    if (updateDoc) {
        return null;
    }
    // check for sub collection
    const isSubCol = context.params.subDocId;

    const parentDoc = `${countersDoc}/${context.params.colId}`;
    const countDoc = isSubCol
        ? `${parentDoc}/${context.params.docId}/${context.params.subColId}`
        : `${parentDoc}`;

    // collection references
    const countRef = db.doc(countDoc);
    const countSnap = await countRef.get();

    // increment size if doc exists
    if (countSnap.exists) {
        // createDoc or deleteDoc
        const n = createDoc ? 1 : -1;
        const i = admin.firestore.FieldValue.increment(n);

        // create event for accurate increment
        const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);

        return db.runTransaction(async (t: any): Promise<any> => {
            const eventSnap = await t.get(eventRef);
            // do nothing if event exists
            if (eventSnap.exists) {
                return null;
            }
            // add event and update size
            await t.update(countRef, { count: i });
            return t.set(eventRef, {
                completed: admin.firestore.FieldValue.serverTimestamp()
            });
        }).catch((e: any) => {
            console.log(e);
        });
        // otherwise count all docs in the collection and add size
    } else {
        const colRef = db.collection(change.after.ref.parent.path);
        return db.runTransaction(async (t: any): Promise<any> => {
            // update size
            const colSnap = await t.get(colRef);
            return t.set(countRef, { count: colSnap.size });
        }).catch((e: any) => {
            console.log(e);
        });;
    }
}

สิ่งนี้จัดการกับเหตุการณ์ส่วนเพิ่มและธุรกรรม ความสวยงามในเรื่องนี้คือถ้าคุณไม่แน่ใจเกี่ยวกับความถูกต้องของเอกสาร (อาจยังอยู่ในช่วงเบต้า) คุณสามารถลบตัวนับเพื่อให้มันเพิ่มโดยอัตโนมัติในทริกเกอร์ถัดไป ใช่ค่าใช้จ่ายนี้ดังนั้นอย่าลบอย่างอื่น

สิ่งเดียวกันที่จะได้รับการนับ:

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

นอกจากนี้คุณอาจต้องการสร้างงาน cron (ฟังก์ชั่นตามกำหนดการ) เพื่อลบกิจกรรมเก่าเพื่อประหยัดเงินในการจัดเก็บฐานข้อมูล อย่างน้อยคุณต้องมีแผนการเผาไหม้และอาจมีการกำหนดค่าเพิ่มเติมบางอย่าง คุณสามารถเรียกใช้ได้ทุกวันอาทิตย์เวลา 23.00 น. https://firebase.google.com/docs/functions/schedule-functions

นี่คือยังไม่ทดลองแต่ควรทำงานกับ tweaks ไม่กี่:

exports.scheduledFunctionCrontab = functions.pubsub.schedule('5 11 * * *')
    .timeZone('America/New_York')
    .onRun(async (context) => {

        // get yesterday
        const yesterday = new Date();
        yesterday.setDate(yesterday.getDate() - 1);

        const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
        const eventFilterSnap = await eventFilter.get();
        eventFilterSnap.forEach(async (doc: any) => {
            await doc.ref.delete();
        });
        return null;
    });

และสุดท้ายอย่าลืมปกป้องคอลเล็กชั่นในfirestoreกฎ:

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

อัปเดต: คำค้นหา

การเพิ่มคำตอบอื่น ๆ ของฉันหากคุณต้องการนับจำนวนการสืบค้นอัตโนมัติคุณสามารถใช้รหัสที่แก้ไขนี้ในฟังก์ชั่นคลาวด์ของคุณ:

    if (col === 'posts') {

        // counter reference - user doc ref
        const userRef = after ? after.userDoc : before.userDoc;
        // query reference
        const postsQuery = db.collection('posts').where('userDoc', "==", userRef);
        // add the count - postsCount on userDoc
        await addCount(change, context, postsQuery, userRef, 'postsCount');

    }
    return delEvents();

ซึ่งจะอัปเดตpostsCountโดยอัตโนมัติใน userDocument คุณสามารถเพิ่มอีกหนึ่งรายการเข้ากับการนับด้วยวิธีนี้ได้อย่างง่ายดาย นี่เป็นเพียงการให้คุณทราบว่าคุณจะทำให้สิ่งต่าง ๆ เป็นอัตโนมัติ ฉันยังให้คุณอีกวิธีหนึ่งในการลบกิจกรรม คุณต้องอ่านแต่ละวันเพื่อลบมันดังนั้นมันจะไม่ช่วยให้คุณลบในภายหลังจริงๆเพียงแค่ทำให้ฟังก์ชั่นช้าลง

/**
 * Adds a counter to a doc
 * @param change - change ref
 * @param context - context ref
 * @param queryRef - the query ref to count
 * @param countRef - the counter document ref
 * @param countName - the name of the counter on the counter document
 */
const addCount = async function (change: any, context: any, 
  queryRef: any, countRef: any, countName: string) {

    // events collection
    const eventsDoc = '_events';

    // simplify event type
    const createDoc = change.after.exists && !change.before.exists;

    // doc references
    const countSnap = await countRef.get();

    // increment size if field exists
    if (countSnap.get(countName)) {
        // createDoc or deleteDoc
        const n = createDoc ? 1 : -1;
        const i = admin.firestore.FieldValue.increment(n);

        // create event for accurate increment
        const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);

        return db.runTransaction(async (t: any): Promise<any> => {
            const eventSnap = await t.get(eventRef);
            // do nothing if event exists
            if (eventSnap.exists) {
                return null;
            }
            // add event and update size
            await t.set(countRef, { [countName]: i }, { merge: true });
            return t.set(eventRef, {
                completed: admin.firestore.FieldValue.serverTimestamp()
            });
        }).catch((e: any) => {
            console.log(e);
        });
        // otherwise count all docs in the collection and add size
    } else {
        return db.runTransaction(async (t: any): Promise<any> => {
            // update size
            const colSnap = await t.get(queryRef);
            return t.set(countRef, { [countName]: colSnap.size }, { merge: true });
        }).catch((e: any) => {
            console.log(e);
        });;
    }
}
/**
 * Deletes events over a day old
 */
const delEvents = async function () {

    // get yesterday
    const yesterday = new Date();
    yesterday.setDate(yesterday.getDate() - 1);

    const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
    const eventFilterSnap = await eventFilter.get();
    eventFilterSnap.forEach(async (doc: any) => {
        await doc.ref.delete();
    });
    return null;
}

ฉันควรเตือนคุณด้วยว่าฟังก์ชั่นสากลจะทำงานในทุก ๆ ช่วงเวลาการโทร อาจมีราคาถูกกว่าที่จะเรียกใช้ฟังก์ชันบน onCreate และ onDelete อินสแตนซ์ของคอลเลกชันเฉพาะของคุณ เช่นเดียวกับฐานข้อมูล noSQL ที่เราใช้รหัสซ้ำและข้อมูลสามารถประหยัดเงินได้


เขียนบทความเกี่ยวกับมันเพื่อให้เข้าถึงได้ง่าย
ahmadalibaloch


0

ใช้เวลาสักครู่ในการทำให้งานนี้ขึ้นอยู่กับคำตอบบางข้อด้านบนดังนั้นฉันคิดว่าฉันจะแบ่งปันให้ผู้อื่นใช้ ฉันหวังว่ามันจะมีประโยชน์

'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();

exports.countDocumentsChange = functions.firestore.document('library/{categoryId}/documents/{documentId}').onWrite((change, context) => {

    const categoryId = context.params.categoryId;
    const categoryRef = db.collection('library').doc(categoryId)
    let FieldValue = require('firebase-admin').firestore.FieldValue;

    if (!change.before.exists) {

        // new document created : add one to count
        categoryRef.update({numberOfDocs: FieldValue.increment(1)});
        console.log("%s numberOfDocs incremented by 1", categoryId);

    } else if (change.before.exists && change.after.exists) {

        // updating existing document : Do nothing

    } else if (!change.after.exists) {

        // deleting document : subtract one from count
        categoryRef.update({numberOfDocs: FieldValue.increment(-1)});
        console.log("%s numberOfDocs decremented by 1", categoryId);

    }

    return 0;
});

0

ฉันลองใช้วิธีการต่าง ๆ มากมาย และสุดท้ายฉันก็ปรับปรุงวิธีการอย่างใดอย่างหนึ่ง ก่อนอื่นคุณต้องสร้างการรวบรวมแยกต่างหากและบันทึกเหตุการณ์ทั้งหมดที่นั่น ประการที่สองคุณต้องสร้างแลมบ์ดาใหม่เพื่อให้ถูกเรียกตามเวลา แลมบ์ดานี้จะนับเหตุการณ์ในการรวบรวมกิจกรรมและเอกสารกิจกรรมที่ชัดเจน รายละเอียดรหัสในบทความ https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca


โปรดระบุรายละเอียดและรหัสที่เกี่ยวข้องในการตอบด้วยตัวเองชี้ให้ผู้คนที่โพสต์บล็อกของคุณไม่ใช่จุดของ StackOverflow
DBS

0

แบบสอบถามนี้จะส่งผลให้จำนวนเอกสาร

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

console.log(count)

1
ไม่ใช่วิธีที่ดีในการดึงเอกสารทั้งหมดจากการรวบรวมทุกครั้ง มันจะมีราคามาก วิธีที่ดีกว่าคือตั้งค่าตัวนับทุกครั้งที่มีการเพิ่มเอกสารใหม่ในคอลเล็กชันนี้เพื่อให้คุณสามารถดึงเอกสารหนึ่งฉบับได้แทนที่จะเป็นสองสามพัน
Corentin Houdayer

0

สิ่งนี้ใช้การนับเพื่อสร้าง ID เฉพาะที่เป็นตัวเลข ในการใช้งานของฉันฉันจะไม่ลดลงเรื่อยๆ แม้ว่าdocumentจะต้องลบ ID นั้นก็ตาม

เมื่อมีcollectionการสร้างที่ต้องการค่าตัวเลขที่ไม่ซ้ำกัน

  1. กำหนดคอลเล็กชันappDataด้วยหนึ่งเอกสารที่setมี.docidonly
  2. ตั้งค่าuniqueNumericIDAmountเป็น 0 ในfirebase firestore console
  3. ใช้doc.data().uniqueNumericIDAmount + 1เป็นรหัสตัวเลขที่เป็นเอกลักษณ์
  4. อัปเดตappDataคอลเล็กชันuniqueNumericIDAmountด้วยfirebase.firestore.FieldValue.increment(1)
firebase
    .firestore()
    .collection("appData")
    .doc("only")
    .get()
    .then(doc => {
        var foo = doc.data();
        foo.id = doc.id;

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

                // upon success of new ID, increment uniqueNumericIDAmount
                firebase
                    .firestore()
                    .collection("appData")
                    .doc("only")
                    .update({
                        uniqueNumericIDAmount: firebase.firestore.FieldValue.increment(
                            1
                        )
                    })
                    .catch(err => {
                        console.log(err);
                    });
            })
            .catch(err => {
                console.log(err);
            });
    });

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

            int Counter = documentSnapshots.size();

        }
    });

1
คำตอบนี้สามารถใช้บริบทเพิ่มเติมเป็นตัวอย่างรหัส
ShellNinja
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.