במצטבר

תיאור

בשלב aggregate(...) מחושבות תוצאות מצטברות (למשל, ספירה, סכום) מהמסמכים שהוחזרו בשלב הקודם.

אופציונלית, אם מספקים ביטוי קיבוץ, המסמכים מקובצים על סמך הביטויים שסופקו, ואז מוחלות פונקציות מצטברות על כל קבוצה.

דוגמאות

בצבירות ללא group-by, השלב aggregate(...) מקבל ביטוי צבירה אחד או יותר עם כינוי:

Node.js

const cities = await db.pipeline()
  .collection("/cities")
  .aggregate(
      countAll().as("total"),
      average("population").as("avg_population")
  )
  .execute();

בצבירות עם קיבוץ, נדרשות קבוצות נוספות מלבד קבוצות הצבירה:

Node.js

const result = await db.pipeline()
  .collectionGroup('citites')
  .aggregate({
    accumulators: [
      countAll().as('cities'),
      field('population').sum().as('total_popoluation')
    ],
    groups: [field('location.state').as('state')]
  })
  .execute();

התנהגות

צבירות ללא קיבוץ

יוצרים אוסף cities עם המסמכים הבאים:

Node.js

await db.collection('cities').doc('SF').set({name: 'San Francisco', state: 'CA', country: 'USA', population: 870000});
await db.collection('cities').doc('LA').set({name: 'Los Angeles', state: 'CA', country: 'USA', population: 3970000});
await db.collection('cities').doc('NY').set({name: 'New York', state: 'NY', country: 'USA', population: 8530000});
await db.collection('cities').doc('TOR').set({name: 'Toronto', state: null, country: 'Canada', population: 2930000});
await db.collection('cities').doc('MEX').set({name: 'Mexico City', state: null, country: 'Mexico', population: 9200000});

כדי לגלות את המספר הכולל של הערים ואת גודל האוכלוסייה הממוצע שלהן:

Node.js

const cities = await db.pipeline()
  .collection("/cities")
  .aggregate(
      countAll().as("total"),
      average("population").as("avg_population")
  )
  .execute();

שמפיק את הפלט:

{avg_population: 5100000, total: 5}

ביצוע צבירות נתונים בקבוצות

אם מספקים ארגומנט groups, אפשר לבצע צבירות על כל קבוצה נפרדת.

לדוגמה, כדי למצוא את העיר עם האוכלוסייה הגדולה ביותר בכל מדינה ובכל מדינה בארה"ב:

Node.js

const cities = await db.pipeline()
  .collection("/cities")
  .aggregate({
      accumulators: [
          countAll().as("number_of_cities"),
          maximum("population").as("max_population")
      ],
      groups: ["country", "state"]
  })
  .execute();

שנותן:

{country: "USA", state: "CA", max_population: 3970000, number_of_cities: 2},
{country: "USA", state: "NY", max_population: 8530000, number_of_cities: 1},
{country: "Canada", state: null, max_population: 2930000, number_of_cities: 1},
{country: "Mexico", state: null,  max_population: 9200000, number_of_cities: 1}

ביטויים מורכבים בקבוצות

בנוסף לקיבוץ לפי ערכי שדות בלבד, שלב aggregate(...) תומך בקיבוץ לפי תוצאות של ביטויים מורכבים. אפשר להשתמש בכל ביטוי שתקף בשלב select(...) כמפתח קיבוץ. כך אפשר ליצור קיבוץ גמיש שמבוסס על ערכים או תנאים מחושבים.

לדוגמה, כדי לקבץ לפי הערך של שדה המדינה (אם הוא null) ולגלות את גודל האוכלוסייה בכל קבוצה:

Node.js

const cities = await db.pipeline()
  .collection("/cities")
  .aggregate({
      accumulators: [
         sum("population").as("total_population")
      ],
      groups: [equal(field("state"), null).as("state_is_null")]
  })
  .execute();

יוחזר:

{state_is_null: true, total_population: 12130000}
{state_is_null: false, total_population: 13370000}

התנהגויות של אתרי אגרגטור

אופן הצבירה של כל פונקציה נתמכת (למשל count, ‏ sum, ‏ avg) מפורט בדף הייעודי בנושא פונקציות צבירה.

התנהגויות מרכזיות בקבוצה

כשמקבצים מסמכים, Firestore משתמש בסמנטיקה של שוויון כדי לקבוע אם ערכים שייכים לאותה קבוצה.

המשמעות היא שערכים שווי ערך, למשל ערכים מספריים שווי ערך מבחינה מתמטית, ללא קשר לסוג המקורי (מספר שלם של 32 ביט, מספר שלם של 64 ביט, מספרים עם נקודה עשרונית, decimal128 וכו'), מקובצים יחד.

לדוגמה, באוסף numerics עם מסמכים שונים שמכילים ערכים של מספר שלם 32 ביט 1, מספר שלם 64 ביט 1L ומספר נקודה צפה 1.0, כולם יצטברו באותה קבוצה.foo הפעלת ספירה עם קיבוץ לפי foo תחזיר:

{foo: 1.0, count: 3}

במקרים כאלה שבהם יש בערכת הנתונים ערכים שונים ששווים זה לזה, ערך הפלט של הקבוצה יכול להיות כל אחד מהערכים האלה. בדוגמה הזו, foo יכול להיות 1,‏ 1L או 1.0.

גם אם נראה שהבחירה היא דטרמיניסטית, לא מומלץ להסתמך על ההתנהגות של בחירת ערך ספציפי.

שימוש בזיכרון

אופן הביצוע של הצבירה תלוי באינדקסים הזמינים. אם אופטימיזציית השאילתות לא בוחרת אינדקס מתאים, צריך לשמור את כל הקבוצות בזיכרון.

אם יש מספר גדול מאוד של קבוצות, או אם כל קבוצה גדולה מאוד (למשל, קיבוץ לפי ערכים גדולים מאוד), יכול להיות שהשלב הזה ייגמר בגלל אין זיכרון פנוי (OOM).

במקרים כאלה, מומלץ להחיל מסננים כדי להגביל את מערך הנתונים לצורך צבירה, לקבץ לפי שדות קטנים יותר או פחות שדות, או ליצור אינדקסים כמומלץ כדי להימנע משימוש רב בזיכרון. התכונה Query Explain תספק מידע על תוכנית ההפעלה של השאילתה ועל נתוני הפרופיל כדי לעזור בניפוי באגים.