במצטבר

תיאור

בשלב 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.

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

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

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

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

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