אגרגציות ב‑REST
כשהשאלה היא “כמה” ולא “אילו” — אגרגציה. במקום למשוך 12,000 שורות ולסכום אותן בקוד, השרת מחזיר שורה אחת לכל קבוצה.
הפרמטרים
Section titled “הפרמטרים”| פרמטר | טיפוס | משמעות |
|---|---|---|
group |
JSON | חובה. אופרטור יחיד בצורה {"<פונקציה>":"<שדה>"}. בלעדיו: {"code":102,"error":"missing parameter for query: group"} |
groupby |
מחרוזת | לפי מה מקבצים — שדה רגיל, Boolean, Date, Pointer, או דוט‑נוטציה דרך Pointer. בלעדיו מתקבלת שורה אחת לכל הטבלה ({"_id":null,"value":…}) — סכום כולל |
where |
JSON | הסינון — אותה שפה בדיוק כמו ב‑שאילתות |
order |
מחרוזת | מיון התוצאות. -value = יורד |
limit |
מספר | הגבלת מספר הקבוצות המוחזרות |
כמו ב‑שאילתות, כל ערך JSON חייב לעבור קידוד URL.
הפונקציות
Section titled “הפונקציות”| הפונקציה | הצורה |
|---|---|
| סכום | group={"sum":"Total"} |
| ספירה | group={"sum":1} |
| ממוצע | group={"avg":"Total"} |
| מינימום / מקסימום | group={"min":"Total"} · group={"max":"Total"} |
| ערך ראשון / אחרון | group={"first":"Total"} · group={"last":"Total"} |
| כל הערכים כמערך | group={"push":"Total"} |
| הערכים הייחודיים כמערך | group={"addToSet":"Total"} |
count, stdDev ו‑stdDevPop אינם אופרטורים מוכרים — לספירה השתמשו ב‑{"sum":1}.
אופרטור אחד בכל בקשה. שני מפתחות ב‑group מוחזרים עם the group must specify exactly one operator,
ושם פונקציה שאינו מוכר עם the group aggregate funciton name <X> must be a valid operator name — שגיאת הכתיב funciton היא במחרוזת שהשרת מחזיר.
שימו לב שאין $ לפני שם הפונקציה, ואין $ לפני שם השדה — {"$sum":"$Total"} נדחה עם אותה הודעה. גם הצורה עם תווית של MCP ({"total":{"sum":"Total"}}) נדחית כאן.
שגיאות שדה (כולן 400 עם code 102): שדה שאינו מספרי ב‑sum/avg — the type of the group aggregate field (<Field>) must be "Number"; שדה לא קיים ב‑groupby — Invalid parameter for groupby: <Field>; group שאינו JSON — group must be valid json. טבלה שאינה קיימת מחזירה {"code":119,"error":"This user is not allowed to access non-existent class: <Table>"}. ההשוואה ל‑/classes/ תלויה באישור שאיתו קוראים: עם Master Key שליפה מ‑/classes/<Table> על טבלה לא קיימת מחזירה 200 ומערך ריק, אבל עם X-Parse-API-Key גם /classes/ מחזיר את אותה 400 עם code 119, כלומר אין שם הבדל בפועל בין שני הנתיבים.
הדוגמה הבסיסית — סכום עסקאות לפי שלב
Section titled “הדוגמה הבסיסית — סכום עסקאות לפי שלב”curl -sS -G "https://api.mbapps.co.il/parse/classes-aggregate/Sales" \ -H "X-Parse-Application-Id: $APP_ID" \ -H "X-Parse-API-Key: $API_KEY" \ --data-urlencode 'group={"sum":"Total"}' \ --data-urlencode 'groupby=SaleStatusId'const APP_ID = process.env.APP_ID, API_KEY = process.env.API_KEY;
const params = new URLSearchParams({ group: '{"sum":"Total"}', groupby: 'SaleStatusId',});
const res = await fetch(`https://api.mbapps.co.il/parse/classes-aggregate/Sales?${params}`, { headers: { 'X-Parse-Application-Id': APP_ID, 'X-Parse-API-Key': API_KEY, },});const data = await res.json();import os, requests
APP_ID = os.environ["APP_ID"]API_KEY = os.environ["API_KEY"]
res = requests.get( "https://api.mbapps.co.il/parse/classes-aggregate/Sales", headers={ "X-Parse-Application-Id": APP_ID, "X-Parse-API-Key": API_KEY, }, params={ "group": '{"sum":"Total"}', "groupby": "SaleStatusId", },)data = res.json()<?php$appId = getenv('APP_ID');$apiKey = getenv('API_KEY');
$query = http_build_query([ 'group' => '{"sum":"Total"}', 'groupby' => 'SaleStatusId',]);
$ch = curl_init();curl_setopt_array($ch, [ CURLOPT_URL => "https://api.mbapps.co.il/parse/classes-aggregate/Sales?$query", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Parse-Application-Id: $appId", "X-Parse-API-Key: $apiKey", ],]);$response = curl_exec($ch);curl_close($ch);$data = json_decode($response, true);{ "results": [ { "_id": "SaleStatuses$i8N34cQBWR", "value": 5000 }, { "_id": "SaleStatuses$zrP1MSVBoq", "value": 3150 }, { "_id": "SaleStatuses$E9cYlAlooc", "value": 1200 }, { "_id": null, "value": 0 } ]}קיבוץ לפי שם — דוט‑נוטציה דרך Pointer
Section titled “קיבוץ לפי שם — דוט‑נוטציה דרך Pointer”זה מייתר את שליפת טבלת ה‑lookup ואת החיבור בקוד:
curl -sS -G "https://api.mbapps.co.il/parse/classes-aggregate/Sales" \ -H "X-Parse-Application-Id: $APP_ID" \ -H "X-Parse-API-Key: $API_KEY" \ --data-urlencode 'group={"sum":"Total"}' \ --data-urlencode 'groupby=SaleStatusId.SaleStatuses.Name'const APP_ID = process.env.APP_ID, API_KEY = process.env.API_KEY;
const params = new URLSearchParams({ group: '{"sum":"Total"}', groupby: 'SaleStatusId.SaleStatuses.Name',});
const res = await fetch(`https://api.mbapps.co.il/parse/classes-aggregate/Sales?${params}`, { headers: { 'X-Parse-Application-Id': APP_ID, 'X-Parse-API-Key': API_KEY, },});const data = await res.json();import os, requests
APP_ID = os.environ["APP_ID"]API_KEY = os.environ["API_KEY"]
res = requests.get( "https://api.mbapps.co.il/parse/classes-aggregate/Sales", headers={ "X-Parse-Application-Id": APP_ID, "X-Parse-API-Key": API_KEY, }, params={ "group": '{"sum":"Total"}', "groupby": "SaleStatusId.SaleStatuses.Name", },)data = res.json()<?php$appId = getenv('APP_ID');$apiKey = getenv('API_KEY');
$query = http_build_query([ 'group' => '{"sum":"Total"}', 'groupby' => 'SaleStatusId.SaleStatuses.Name',]);
$ch = curl_init();curl_setopt_array($ch, [ CURLOPT_URL => "https://api.mbapps.co.il/parse/classes-aggregate/Sales?$query", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Parse-Application-Id: $appId", "X-Parse-API-Key: $apiKey", ],]);$response = curl_exec($ch);curl_close($ch);$data = json_decode($response, true);{ "results": [ { "_id": "פגישת מצגת", "value": 5000 }, { "_id": "הושלמה", "value": 3150 }, { "_id": "חדש", "value": 1200 }, { "value": 0 } ]}התבנית היא <PointerField>.<TargetClass>.<TargetField> — אותה דוט‑נוטציה שמשמשת בדוחות ובטריגרים. ראו סוגי נתונים.
סינון, מיון והגבלה
Section titled “סינון, מיון והגבלה”curl -sS -G "https://api.mbapps.co.il/parse/classes-aggregate/Sales" \ -H "X-Parse-Application-Id: $APP_ID" \ -H "X-Parse-API-Key: $API_KEY" \ --data-urlencode 'where={"ClosingDate":{"$gte":{"__type":"Date","iso":"2026-01-01T00:00:00.000Z"}}}' \ --data-urlencode 'group={"sum":"Total"}' \ --data-urlencode 'groupby=OwnerId._User.name' \ --data-urlencode 'order=-value' \ --data-urlencode 'limit=10'const APP_ID = process.env.APP_ID, API_KEY = process.env.API_KEY;
const params = new URLSearchParams({ where: '{"ClosingDate":{"$gte":{"__type":"Date","iso":"2026-01-01T00:00:00.000Z"}}}', group: '{"sum":"Total"}', groupby: 'OwnerId._User.name', order: '-value', limit: '10',});
const res = await fetch(`https://api.mbapps.co.il/parse/classes-aggregate/Sales?${params}`, { headers: { 'X-Parse-Application-Id': APP_ID, 'X-Parse-API-Key': API_KEY, },});const data = await res.json();import os, requests
APP_ID = os.environ["APP_ID"]API_KEY = os.environ["API_KEY"]
res = requests.get( "https://api.mbapps.co.il/parse/classes-aggregate/Sales", headers={ "X-Parse-Application-Id": APP_ID, "X-Parse-API-Key": API_KEY, }, params={ "where": '{"ClosingDate":{"$gte":{"__type":"Date","iso":"2026-01-01T00:00:00.000Z"}}}', "group": '{"sum":"Total"}', "groupby": "OwnerId._User.name", "order": "-value", "limit": "10", },)data = res.json()<?php$appId = getenv('APP_ID');$apiKey = getenv('API_KEY');
$query = http_build_query([ 'where' => '{"ClosingDate":{"$gte":{"__type":"Date","iso":"2026-01-01T00:00:00.000Z"}}}', 'group' => '{"sum":"Total"}', 'groupby' => 'OwnerId._User.name', 'order' => '-value', 'limit' => '10',]);
$ch = curl_init();curl_setopt_array($ch, [ CURLOPT_URL => "https://api.mbapps.co.il/parse/classes-aggregate/Sales?$query", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Parse-Application-Id: $appId", "X-Parse-API-Key: $apiKey", ],]);$response = curl_exec($ch);curl_close($ch);$data = json_decode($response, true);עשרת אנשי המכירות עם ההיקף הגדול ביותר, מעסקאות שמועד סגירתן מ‑2026. order מקבל value, -value או _id.
הרשאות
Section titled “הרשאות”האגרגציה אינה דורשת Master Key. היא מכבדת את הרשאות הטבלה כמו כל שליפה אחרת:
| האישור | התוצאה |
|---|---|
| Master Key / API Key | עובד |
Session Token של משתמש עם הרשאת find על הטבלה |
עובד |
| Application Id בלבד, או משתמש בלי תפקיד | {"code":119,"error":"Permission denied for action find on class Sales."} |
בקיבוץ דוט‑נוטציה צריך find גם על מחלקת היעד של ה‑Pointer, לא רק על הטבלה שמקבצים — ראו את האזהרה בסעיף הדוט‑נוטציה למעלה.
עדיין אל תריצו אגרגציה מהדפדפן עם מפתח: המפתח שפותח אותה פותח גם את כל השאר. הדפוס הנכון — שרת ביניים שמחזיק את המפתח, מריץ את האגרגציה, ומחזיר ללקוח רק את המספרים.
שרת ביניים — הדפוס המומלץ
Section titled “שרת ביניים — הדפוס המומלץ”"""דוח פייפליין: סכום ומספר עסקאות לפי שלב, עם שמות בעברית.המפתח נשאר בשרת; הלקוח מקבל JSON מוכן."""import json, os, requests
API = "https://api.mbapps.co.il/parse"H = {"X-Parse-Application-Id": os.environ["MB_APP_ID"], "X-Parse-API-Key": os.environ["MB_API_KEY"]} # ממנהל סודות, לא מקובץ
def pipeline_report(from_iso: str) -> list[dict]: common = { "where": json.dumps({"createdAt": {"$gte": {"__type": "Date", "iso": from_iso}}}), "groupby": "SaleStatusId.SaleStatuses.Name", } def agg(op): params = {**common, "group": json.dumps(op)} r = requests.get(f"{API}/classes-aggregate/Sales", headers=H, params=params, timeout=60) r.raise_for_status() return {row.get("_id"): row["value"] for row in r.json()["results"]}
totals = agg({"sum": "Total"}) # אופרטור אחד לכל קריאה — counts = agg({"sum": 1}) # ולכן סכום וספירה הם שתי קריאות
return [{"stage": stage or "— ללא שלב —", "total": total, "deals": counts.get(stage, 0)} for stage, total in sorted(totals.items(), key=lambda kv: -kv[1])]
if __name__ == "__main__": for row in pipeline_report("2026-01-01T00:00:00.000Z"): print(f'{row["stage"]:<24} {row["total"]:>12,} ({row["deals"]} עסקאות)')שלוש מגבלות שכדאי להכיר
Section titled “שלוש מגבלות שכדאי להכיר”1 · אופרטור אחד לכל קריאה
Section titled “1 · אופרטור אחד לכל קריאה”אין דרך לקבל סכום וגם ספירה בקריאה אחת — זו הסיבה שהדוגמה למעלה מריצה שתי קריאות ומחברת אותן בקוד.
בשכבת ה‑MCP הכלי Aggregate-Data גמיש יותר: הוא מקבל group עם תווית ({"total":{"sum":"Total"}}, והערך חוזר תחת אותה תווית במקום value), ואף שתי תוויות בקריאה אחת ({"total":{"sum":"Total"},"n":{"sum":1}} — למרות שסכימת הכלי מצהירה על אחת); groupby יכול להיות
מחרוזת או אובייקט לקיבוץ רב‑מימדי ({"stage":"SaleStatusId.SaleStatuses.Name","month":{"mm":"ClosingDate"}}), כולל קיבוץ לפי תאריך: hour, dow, mm, q, q/yy, yy עובדים; m/yy נכשל (Invalid parameter for groupby date). ה‑where של הכלי חסום כרגע בוולידציית fieldName, ולכן סינון נעשה בינתיים רק ב‑REST.
2 · classes-aggregate אינו נתיב מתועד ב‑Parse
Section titled “2 · classes-aggregate אינו נתיב מתועד ב‑Parse”3 · שדה מחושב לא קיים
Section titled “3 · שדה מחושב לא קיים”אין שדות מחושבים ברמת הסכימה ואין “סכום שורות” אוטומטי. חישוב חוצה‑רשומות שצריך להישמר על רשומה (עמלות מדורגות, סיכומי גלגול) הוא פונקציית צד שרת — ראו פונקציות צד שרת.
המשך מכאן
Section titled “המשך מכאן”- שאילתות — כשצריך את השורות עצמן
- מפת הטבלאות — לאיזה שדה בכלל מקבצים
- אימות והרשאות — למה מפתח לא נוגע בדפדפן
- שרת ה‑MCP —
Aggregate-Dataכשכבה גבוהה יותר - מגבלות ומכסות