במסמך הזה נסביר את העקרונות הבסיסיים של אחזור נתוני מסד נתונים, את אופן הסדר של הנתונים ואת האופן שבו מבצעים שאילתות פשוטות על הנתונים. אחזור הנתונים ב-Admin SDK מיושם באופן שונה במקצת בשפות תכנות שונות.
- מאזינים אסינכרונים: כדי לאחזר נתונים שמאוחסנים ב-Firebase Realtime Database, צריך לצרף מאזין אסינכרוני להפניה למסד נתונים. המאזין מופעל פעם אחת עבור המצב הראשוני של הנתונים, ופעם נוספת בכל פעם שהנתונים משתנים. יכול להיות שמאזין לאירועים יקבל כמה סוגים של אירועים. מצב אחזור הנתונים הזה נתמך ב-SDK לניהול של Java, Node.js ו-Python.
- קריאות חסימה: כדי לאחזר נתונים שמאוחסנים ב-Firebase Realtime Database, מפעילים שיטה חסימה על הפניה למסד נתונים, שמחזירה את הנתונים שמאוחסנים בהפניה. כל קריאה ל-method היא פעולה חד-פעמית. כלומר, ה-SDK לא רושם קריאות חזרה (callbacks) שמקשיבות לעדכוני נתונים נוספים. המודל הזה לאחזור נתונים נתמך ב-Python וב-Go Admin SDK.
תחילת העבודה
נבחן שוב את הדוגמה לבלוג מהמאמר הקודם כדי להבין איך קוראים נתונים ממסד נתונים של Firebase. חשוב לזכור שהפוסטים בבלוג באפליקציית הדוגמה מאוחסנים בכתובת ה-URL של מסד הנתונים https://docs-examples.firebaseio.com/server/saving-data/fireblog/posts.json. כדי לקרוא את נתוני הפוסט, אפשר:
Java
public static class Post { public String author; public String title; public Post(String author, String title) { // ... } } // Get a reference to our posts final FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("server/saving-data/fireblog/posts"); // Attach a listener to read the data at our posts reference ref.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { Post post = dataSnapshot.getValue(Post.class); System.out.println(post); } @Override public void onCancelled(DatabaseError databaseError) { System.out.println("The read failed: " + databaseError.getCode()); } });
Node.js
// Get a database reference to our posts const db = getDatabase(); const ref = db.ref('server/saving-data/fireblog/posts'); // Attach an asynchronous callback to read the data at our posts reference ref.on('value', (snapshot) => { console.log(snapshot.val()); }, (errorObject) => { console.log('The read failed: ' + errorObject.name); });
Python
# Import database module. from firebase_admin import db # Get a database reference to our posts ref = db.reference('server/saving-data/fireblog/posts') # Read the data at the posts reference (this is a blocking operation) print(ref.get())
Go
// Post is a json-serializable type. type Post struct { Author string `json:"author,omitempty"` Title string `json:"title,omitempty"` } // Create a database client from App. client, err := app.Database(ctx) if err != nil { log.Fatalln("Error initializing database client:", err) } // Get a database reference to our posts ref := client.NewRef("server/saving-data/fireblog/posts") // Read the data at the posts reference (this is a blocking operation) var post Post if err := ref.Get(ctx, &post); err != nil { log.Fatalln("Error reading value:", err) }
אם מריצים את הקוד שלמעלה, מופיע אובייקט שמכיל את כל הפוסטים שתועדו במסוף. ב-Node.js וב-Java, פונקציית המאזין נקראת בכל פעם שמתווספים נתונים חדשים להפניה למסד הנתונים, ואין צורך לכתוב קוד נוסף כדי שזה יקרה.
ב-Java וב-Node.js, פונקציית הקריאה החוזרת מקבלת DataSnapshot
, שהוא קובץ snapshot של הנתונים. תמונת מצב היא תמונה של הנתונים בהפניה מסוימת למסד נתונים בנקודת זמן מסוימת. קריאה ל-val()
או ל-getValue()
ב-snapshot מחזירה ייצוג של הנתונים באובייקט שספציפי לשפה. אם לא קיימים נתונים במיקום של הפניה, הערך של קובץ ה-snapshot הוא null
. השיטה get()
ב-Python מחזירה ייצוג של הנתונים ב-Python ישירות. הפונקציה Get()
ב-Go מבצעת unmarshaling של הנתונים למבנה נתונים נתון.
שימו לב שבדוגמה שלמעלה השתמשנו בסוג האירוע value
, שמקריא את כל התוכן של הפניה למסד נתונים של Firebase, גם אם רק פריט נתונים אחד השתנה. value
הוא אחד מחמשת סוגי האירועים השונים שמפורטים בהמשך, שאפשר להשתמש בהם כדי לקרוא נתונים ממסד הנתונים.
קריאת סוגי אירועים ב-Java וב-Node.js
ערך
האירוע value
משמש לקריאת קובץ snapshot סטטי של התוכן בנתיב נתון של מסד נתונים, כפי שהיה קיים בזמן אירוע הקריאה. הוא מופעל פעם אחת עם הנתונים הראשוניים ופעם נוספת בכל פעם שהנתונים משתנים. בקריאה החוזרת של האירוע מועברת קובץ snapshot שמכיל את כל הנתונים במיקום הזה, כולל נתוני הצאצאים. בדוגמת הקוד שלמעלה, הפונקציה value
החזירה את כל פוסטי הבלוג באפליקציה. בכל פעם שמתווסף פוסט חדש בבלוג, פונקציית הקריאה החוזרת מחזירה את כל הפוסטים.
הוספת צאצא
בדרך כלל משתמשים באירוע child_added
כשמאחזרים רשימת פריטים ממסד הנתונים. בניגוד ל-value
שמחזיר את כל התוכן של המיקום, הפונקציה child_added
מופעלת פעם אחת לכל צאצא קיים, ואז שוב בכל פעם שנוסף צאצא חדש לנתיב שצוין. בקריאה החוזרת (callback) של האירוע מועברת תמונת מצב שמכילה את הנתונים של הצאצא החדש. לצורך סדר, מועבר אליו גם ארגומנט שני שמכיל את המפתח של הצאצא הקודם.
אם רוצים לאחזר רק את הנתונים של כל פוסט חדש שנוסף לאפליקציית הבלוג, אפשר להשתמש ב-child_added
:
Java
ref.addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { Post newPost = dataSnapshot.getValue(Post.class); System.out.println("Author: " + newPost.author); System.out.println("Title: " + newPost.title); System.out.println("Previous Post ID: " + prevChildKey); } @Override public void onChildChanged(DataSnapshot dataSnapshot, String prevChildKey) {} @Override public void onChildRemoved(DataSnapshot dataSnapshot) {} @Override public void onChildMoved(DataSnapshot dataSnapshot, String prevChildKey) {} @Override public void onCancelled(DatabaseError databaseError) {} });
Node.js
// Retrieve new posts as they are added to our database ref.on('child_added', (snapshot, prevChildKey) => { const newPost = snapshot.val(); console.log('Author: ' + newPost.author); console.log('Title: ' + newPost.title); console.log('Previous Post ID: ' + prevChildKey); });
בדוגמה הזו, קובץ snapshot יכיל אובייקט עם פוסט בודד בבלוג. ה-SDK ממיר פוסטים לאובייקטים על ידי אחזור הערך, כך שיש לכם גישה למאפייני השם והמחבר של הפוסטים באמצעות קריאה ל-author
ול-title
, בהתאמה. יש לכם גם גישה למזהה הפוסט הקודם מהארגומנט השני של prevChildKey
.
הילד השתנה
האירוע child_changed
מופעל בכל פעם שצומת צאצא משתנה. זה כולל כל שינוי שנעשה בצאצאים של צומת הצאצא. בדרך כלל משתמשים בו בשילוב עם child_added
ו-child_removed
כדי להגיב לשינויים ברשימת פריטים. תמונת המצב שהועברה ל-event callback מכילה את הנתונים המעודכנים של הצאצא.
אפשר להשתמש ב-child_changed
כדי לקרוא נתונים מעודכנים בפוסטים בבלוג כשהם עוברים עריכה:
Java
ref.addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {} @Override public void onChildChanged(DataSnapshot dataSnapshot, String prevChildKey) { Post changedPost = dataSnapshot.getValue(Post.class); System.out.println("The updated post title is: " + changedPost.title); } @Override public void onChildRemoved(DataSnapshot dataSnapshot) {} @Override public void onChildMoved(DataSnapshot dataSnapshot, String prevChildKey) {} @Override public void onCancelled(DatabaseError databaseError) {} });
Node.js
// Get the data on a post that has changed ref.on('child_changed', (snapshot) => { const changedPost = snapshot.val(); console.log('The updated post title is ' + changedPost.title); });
הילד הוסר
האירוע child_removed
מופעל כאשר צאצא מיידי מסוים מוסר. בדרך כלל משתמשים בו בשילוב עם child_added
ו-child_changed
. קובץ snapshot שמוענק ל-call back של האירוע מכיל את הנתונים של הצאצא שהוסר.
בדוגמה של הבלוג, אפשר להשתמש ב-child_removed
כדי לתעד במסוף התראה על הפוסט שנמחק:
Java
ref.addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {} @Override public void onChildChanged(DataSnapshot dataSnapshot, String prevChildKey) {} @Override public void onChildRemoved(DataSnapshot dataSnapshot) { Post removedPost = dataSnapshot.getValue(Post.class); System.out.println("The blog post titled " + removedPost.title + " has been deleted"); } @Override public void onChildMoved(DataSnapshot dataSnapshot, String prevChildKey) {} @Override public void onCancelled(DatabaseError databaseError) {} });
Node.js
// Get a reference to our posts const ref = db.ref('server/saving-data/fireblog/posts'); // Get the data on a post that has been removed ref.on('child_removed', (snapshot) => { const deletedPost = snapshot.val(); console.log('The blog post titled \'' + deletedPost.title + '\' has been deleted'); });
הילד עבר לכתובת אחרת
האירוע child_moved
משמש לעבודה עם נתונים מסודרים, כפי שמתואר בקטע הבא.
ערבויות לאירועים
מסדי הנתונים של Firebase מבטיחים כמה דברים חשובים לגבי אירועים:
ערבויות לאירועים במסד נתונים |
---|
אירועים תמיד מופעלים כשהמצב המקומי משתנה. |
בסופו של דבר, האירועים תמיד ישקפו את המצב הנכון של הנתונים, גם במקרים שבהם פעולות מקומיות או תזמון גורמים להבדלים זמניים, כמו אובדן זמני של חיבור לרשת. |
כתיבה מלקוח יחיד תמיד תירשם בשרת ותשודר למשתמשים אחרים לפי הסדר. |
אירועי הערך מופעלים תמיד אחרונים, והם תמיד מכילים עדכונים מכל האירועים האחרים שהתרחשו לפני צילום קובץ ה-snapshot. |
מאחר שאירועי ערך מופעלים תמיד אחרונים, הדוגמה הבאה תמיד תפעל:
Java
final AtomicInteger count = new AtomicInteger(); ref.addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { // New child added, increment count int newCount = count.incrementAndGet(); System.out.println("Added " + dataSnapshot.getKey() + ", count is " + newCount); } // ... }); // The number of children will always be equal to 'count' since the value of // the dataSnapshot here will include every child_added event triggered before this point. ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { long numChildren = dataSnapshot.getChildrenCount(); System.out.println(count.get() + " == " + numChildren); } @Override public void onCancelled(DatabaseError databaseError) {} });
Node.js
let count = 0; ref.on('child_added', (snap) => { count++; console.log('added:', snap.key); }); // length will always equal count, since snap.val() will include every child_added event // triggered before this point ref.once('value', (snap) => { console.log('initial data loaded!', snap.numChildren() === count); });
ניתוק של קריאות חוזרות (callbacks)
כדי להסיר פונקציות קריאה חוזרת, מציינים את סוג האירוע ואת פונקציית הקריאה החוזרת שרוצים להסיר, כמו בדוגמה הבאה:
Java
// Create and attach listener ValueEventListener listener = new ValueEventListener() { // ... }; ref.addValueEventListener(listener); // Remove listener ref.removeEventListener(listener);
Node.js
ref.off('value', originalCallback);
אם העברת הקשר היקף אל on()
, צריך להעביר אותו גם כשמנתקים את הפונקציה הלא סטטית להפעלה חוזרת:
Java
// Not applicable for Java
Node.js
ref.off('value', originalCallback, ctx);
כדי להסיר את כל הקריאות החוזרות למיקום מסוים, מבצעים את הפעולות הבאות:
Java
// No Java equivalent, listeners must be removed individually.
Node.js
// Remove all value callbacks ref.off('value'); // Remove all callbacks of any type ref.off();
קריאת נתונים פעם אחת
במקרים מסוימים, כדאי להפעיל קריאה חוזרת פעם אחת ואז להסיר אותה באופן מיידי. כדי להקל עליכם, יצרנו פונקציית עזר:
Java
ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // ... } @Override public void onCancelled(DatabaseError databaseError) { // ... } });
Node.js
ref.once('value', (data) => { // do some stuff once });
Python
# Import database module. from firebase_admin import db # Get a database reference to our posts ref = db.reference('server/saving-data/fireblog/posts') # Read the data at the posts reference (this is a blocking operation) print(ref.get())
Go
// Create a database client from App. client, err := app.Database(ctx) if err != nil { log.Fatalln("Error initializing database client:", err) } // Get a database reference to our posts ref := client.NewRef("server/saving-data/fireblog/posts") // Read the data at the posts reference (this is a blocking operation) var post Post if err := ref.Get(ctx, &post); err != nil { log.Fatalln("Error reading value:", err) }
שליחת שאילתות לנתונים
בעזרת שאילתות למסדי נתונים של Firebase, אפשר לאחזר נתונים באופן סלקטיבי על סמך גורמים שונים. כדי ליצור שאילתה במסד הנתונים, קודם צריך לציין איך רוצים שהנתונים ימוינו באמצעות אחת מפונקציות המיון: orderByChild()
, orderByKey()
או orderByValue()
. לאחר מכן אפשר לשלב אותן עם חמש שיטות אחרות כדי לבצע שאילתות מורכבות: limitToFirst()
, limitToLast()
, startAt()
, endAt()
ו-equalTo()
.
אנחנו ב-Firebase חושבים שדינוזאורים הם מגניבים, ולכן נשתמש בקטע קוד ממסד נתונים לדוגמה של עובדות על דינוזאורים כדי להדגים איך שולחים שאילתות על נתונים במסד הנתונים של Firebase:
{ "lambeosaurus": { "height" : 2.1, "length" : 12.5, "weight": 5000 }, "stegosaurus": { "height" : 4, "length" : 9, "weight" : 2500 } }
אפשר לסדר את הנתונים בשלוש דרכים: לפי מפתח צאצא, לפי מפתח או לפי ערך. שאילתה בסיסית של מסד נתונים מתחילה באחת מפונקציות הסדר האלה, שמפורטות בהמשך.
מיון לפי מפתח צאצא ספציפי
כדי למיין צמתים לפי מפתח צאצא משותף, מעבירים את המפתח הזה ל-orderByChild()
. לדוגמה, כדי לקרוא את כל הדינוזאורים לפי גובה, אפשר לבצע את הפעולות הבאות:
Java
public static class Dinosaur { public int height; public int weight; public Dinosaur(int height, int weight) { // ... } } final DatabaseReference dinosaursRef = database.getReference("dinosaurs"); dinosaursRef.orderByChild("height").addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { Dinosaur dinosaur = dataSnapshot.getValue(Dinosaur.class); System.out.println(dataSnapshot.getKey() + " was " + dinosaur.height + " meters tall."); } // ... });
Node.js
const ref = db.ref('dinosaurs'); ref.orderByChild('height').on('child_added', (snapshot) => { console.log(snapshot.key + ' was ' + snapshot.val().height + ' meters tall'); });
Python
ref = db.reference('dinosaurs') snapshot = ref.order_by_child('height').get() for key, val in snapshot.items(): print('{0} was {1} meters tall'.format(key, val))
Go
// Dinosaur is a json-serializable type. type Dinosaur struct { Height int `json:"height"` Width int `json:"width"` } ref := client.NewRef("dinosaurs") results, err := ref.OrderByChild("height").GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } for _, r := range results { var d Dinosaur if err := r.Unmarshal(&d); err != nil { log.Fatalln("Error unmarshaling result:", err) } fmt.Printf("%s was %d meteres tall", r.Key(), d.Height) }
כל צומת שאין לו את מפתח הצאצא שאנחנו שולחים עליו שאילתה ממוין לפי הערך null
, כלומר הוא יופיע ראשון בסדר. פרטים על סדר הנתונים מופיעים בקטע סדר הנתונים.
אפשר גם למיין שאילתות לפי צאצאים שמוטמעים לעומק, ולא רק לפי צאצאים ברמה אחת למטה. האפשרות הזו שימושית אם יש לכם נתונים שמקובצים לעומק, כמו זה:
{ "lambeosaurus": { "dimensions": { "height" : 2.1, "length" : 12.5, "weight": 5000 } }, "stegosaurus": { "dimensions": { "height" : 4, "length" : 9, "weight" : 2500 } } }
כדי לשלוח שאילתות לגבי הגובה עכשיו, אפשר להשתמש בנתיב המלא לאובייקט במקום במפתח יחיד:
Java
dinosaursRef.orderByChild("dimensions/height").addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { // ... } // ... });
Node.js
const ref = db.ref('dinosaurs'); ref.orderByChild('dimensions/height').on('child_added', (snapshot) => { console.log(snapshot.key + ' was ' + snapshot.val().height + ' meters tall'); });
Python
ref = db.reference('dinosaurs') snapshot = ref.order_by_child('dimensions/height').get() for key, val in snapshot.items(): print('{0} was {1} meters tall'.format(key, val))
Go
ref := client.NewRef("dinosaurs") results, err := ref.OrderByChild("dimensions/height").GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } for _, r := range results { var d Dinosaur if err := r.Unmarshal(&d); err != nil { log.Fatalln("Error unmarshaling result:", err) } fmt.Printf("%s was %d meteres tall", r.Key(), d.Height) }
אפשר למיין שאילתות לפי מפתח אחד בלבד בכל פעם. קריאה ל-orderByChild()
מספר פעמים באותה שאילתה תגרום להצגת שגיאה.
סידור לפי מפתח
אפשר גם למיין צמתים לפי המפתחות שלהם באמצעות השיטה orderByKey()
. בדוגמה הבאה מוצגת קריאה של כל הדינוזאורים בסדר אלפביתי:
Java
dinosaursRef.orderByKey().addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { System.out.println(dataSnapshot.getKey()); } // ... });
Node.js
var ref = db.ref('dinosaurs'); ref.orderByKey().on('child_added', (snapshot) => { console.log(snapshot.key); });
Python
ref = db.reference('dinosaurs') snapshot = ref.order_by_key().get() print(snapshot)
Go
ref := client.NewRef("dinosaurs") results, err := ref.OrderByKey().GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } snapshot := make([]Dinosaur, len(results)) for i, r := range results { var d Dinosaur if err := r.Unmarshal(&d); err != nil { log.Fatalln("Error unmarshaling result:", err) } snapshot[i] = d } fmt.Println(snapshot)
מיון לפי ערך
אפשר למיין צמתים לפי הערך של מפתחות הצאצאים שלהם באמצעות השיטה orderByValue()
. נניח שהדינוזאורים מקיימים תחרות ספורט דינוזאורים ואתם עוקבים אחרי התוצאות שלהם בפורמט הבא:
{ "scores": { "bruhathkayosaurus" : 55, "lambeosaurus" : 21, "linhenykus" : 80, "pterodactyl" : 93, "stegosaurus" : 5, "triceratops" : 22 } }
כדי למיין את הדינוזאורים לפי הציון שלהם, אפשר ליצור את השאילתה הבאה:
Java
DatabaseReference scoresRef = database.getReference("scores"); scoresRef.orderByValue().addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { System.out.println("The " + dataSnapshot.getKey() + " score is " + dataSnapshot.getValue()); } // ... });
Node.js
const scoresRef = db.ref('scores'); scoresRef.orderByValue().on('value', (snapshot) => { snapshot.forEach((data) => { console.log('The ' + data.key + ' dinosaur\'s score is ' + data.val()); }); });
Python
ref = db.reference('scores') snapshot = ref.order_by_value().get() for key, val in snapshot.items(): print('The {0} dinosaur\'s score is {1}'.format(key, val))
Go
ref := client.NewRef("scores") results, err := ref.OrderByValue().GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } for _, r := range results { var score int if err := r.Unmarshal(&score); err != nil { log.Fatalln("Error unmarshaling result:", err) } fmt.Printf("The %s dinosaur's score is %d\n", r.Key(), score) }
בקטע איך הנתונים ממוינים מוסבר איך מתבצעת המיון של ערכים מסוג null
, בוליאני, מחרוזת ואובייקט כשמשתמשים ב-orderByValue()
.
שאילתות מורכבות
עכשיו, כשברור איך הנתונים מסודרים, אפשר להשתמש בשיטות limit או range שמתוארות בהמשך כדי ליצור שאילתות מורכבות יותר.
הגבלת שאילתות
השאילתות limitToFirst()
ו-limitToLast()
משמשות להגדרת מספר הילדים המקסימלי שיסונכרנו בקריאה חוזרת נתונה. אם תגדירו מגבלה של 100, בהתחלה תקבלו רק עד 100 אירועי child_added
. אם יש פחות מ-100 הודעות שמורות במסד הנתונים, אירוע child_added
יופעל לכל הודעה. עם זאת, אם יש לכם יותר מ-100 הודעות, תקבלו אירוע child_added
רק לגבי 100 מההודעות האלה. אלה 100 ההודעות הראשונות לפי סדר אם משתמשים ב-limitToFirst()
, או 100 ההודעות האחרונות לפי סדר אם משתמשים ב-limitToLast()
. ככל שהפריטים משתנים, תקבלו אירועי child_added
על פריטים שמתווספים לשאילתה ואירועי child_removed
על פריטים שיוצאים ממנה, כך שהמספר הכולל יישאר 100.
בעזרת מסד הנתונים של עובדות על דינוזאורים ו-orderByChild()
, אפשר למצוא את שני הדינוזאורים הכי כבדים:
Java
dinosaursRef.orderByChild("weight").limitToLast(2).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { System.out.println(dataSnapshot.getKey()); } // ... });
Node.js
const ref = db.ref('dinosaurs'); ref.orderByChild('weight').limitToLast(2).on('child_added', (snapshot) => { console.log(snapshot.key); });
Python
ref = db.reference('dinosaurs') snapshot = ref.order_by_child('weight').limit_to_last(2).get() for key in snapshot: print(key)
Go
ref := client.NewRef("dinosaurs") results, err := ref.OrderByChild("weight").LimitToLast(2).GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } for _, r := range results { fmt.Println(r.Key()) }
פונקציית ה-callback child_added
מופעלת בדיוק פעמיים, אלא אם יש פחות משני דינוזאורים שמאוחסנים במסד הנתונים. הוא יופעל גם לכל דינוזאור חדש וכבד יותר שיתווסף למסד הנתונים.
ב-Python, השאילתה מחזירה ישירות OrderedDict
שמכיל את שני הדינוזאורים הכבדים ביותר.
באופן דומה, אפשר למצוא את שני הדינוזאורים הקצרים ביותר באמצעות limitToFirst()
:
Java
dinosaursRef.orderByChild("weight").limitToFirst(2).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { System.out.println(dataSnapshot.getKey()); } // ... });
Node.js
const ref = db.ref('dinosaurs'); ref.orderByChild('height').limitToFirst(2).on('child_added', (snapshot) => { console.log(snapshot.key); });
Python
ref = db.reference('dinosaurs') snapshot = ref.order_by_child('height').limit_to_first(2).get() for key in snapshot: print(key)
Go
ref := client.NewRef("dinosaurs") results, err := ref.OrderByChild("height").LimitToFirst(2).GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } for _, r := range results { fmt.Println(r.Key()) }
פונקציית ה-callback child_added
מופעלת בדיוק פעמיים, אלא אם יש פחות משני דינוזאורים שמאוחסנים במסד הנתונים. הפונקציה תופעל שוב גם אם אחד משני הדינוזאורים הראשונים יוסר ממסד הנתונים, כי דינוזאור חדש יהיה הדינוזאור השני הקצר ביותר. ב-Python, השאילתה מחזירה ישירות OrderedDict
שמכיל את הדינוזאורים הקצרים ביותר.
אפשר גם להריץ שאילתות עם הגבלות באמצעות orderByValue()
. אם רוצים ליצור לוח מנהיגות עם 3 הדינוזאורים המובילים עם הציון הגבוה ביותר בספורט הדינוזאורים, אפשר לבצע את הפעולות הבאות:
Java
scoresRef.orderByValue().limitToFirst(3).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { System.out.println("The " + dataSnapshot.getKey() + " score is " + dataSnapshot.getValue()); } // ... });
Node.js
const scoresRef = db.ref('scores'); scoresRef.orderByValue().limitToLast(3).on('value', (snapshot) =>{ snapshot.forEach((data) => { console.log('The ' + data.key + ' dinosaur\'s score is ' + data.val()); }); });
Python
scores_ref = db.reference('scores') snapshot = scores_ref.order_by_value().limit_to_last(3).get() for key, val in snapshot.items(): print('The {0} dinosaur\'s score is {1}'.format(key, val))
Go
ref := client.NewRef("scores") results, err := ref.OrderByValue().LimitToLast(3).GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } for _, r := range results { var score int if err := r.Unmarshal(&score); err != nil { log.Fatalln("Error unmarshaling result:", err) } fmt.Printf("The %s dinosaur's score is %d\n", r.Key(), score) }
שאילתות טווח
בעזרת startAt()
, endAt()
ו-equalTo()
אפשר לבחור נקודות התחלה וסיום שרירותיות לשאילתות. לדוגמה, אם רוצים למצוא את כל הדינוזאורים שגובהם לפחות שלושה מטרים, אפשר לשלב את orderByChild()
ו-startAt()
:
Java
dinosaursRef.orderByChild("height").startAt(3).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { System.out.println(dataSnapshot.getKey()); } // ... });
Node.js
const ref = db.ref('dinosaurs'); ref.orderByChild('height').startAt(3).on('child_added', (snapshot) => { console.log(snapshot.key); });
Python
ref = db.reference('dinosaurs') snapshot = ref.order_by_child('height').start_at(3).get() for key in snapshot: print(key)
Go
ref := client.NewRef("dinosaurs") results, err := ref.OrderByChild("height").StartAt(3).GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } for _, r := range results { fmt.Println(r.Key()) }
אפשר להשתמש ב-endAt()
כדי למצוא את כל הדינוזאורים ששמותיהם מופיעים לפני Pterodactyl מבחינה לקסיקוגרפית:
Java
dinosaursRef.orderByKey().endAt("pterodactyl").addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { System.out.println(dataSnapshot.getKey()); } // ... });
Node.js
const ref = db.ref('dinosaurs'); ref.orderByKey().endAt('pterodactyl').on('child_added', (snapshot) => { console.log(snapshot.key); });
Python
ref = db.reference('dinosaurs') snapshot = ref.order_by_key().end_at('pterodactyl').get() for key in snapshot: print(key)
Go
ref := client.NewRef("dinosaurs") results, err := ref.OrderByKey().EndAt("pterodactyl").GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } for _, r := range results { fmt.Println(r.Key()) }
אפשר לשלב בין startAt()
ל-endAt()
כדי להגביל את שני הקצוות של השאילתה. בדוגמה הבאה מוצגים כל הדינוזאורים שהשם שלהם מתחיל באות 'b':
Java
dinosaursRef.orderByKey().startAt("b").endAt("b\uf8ff").addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { System.out.println(dataSnapshot.getKey()); } // ... });
Node.js
var ref = db.ref('dinosaurs'); ref.orderByKey().startAt('b').endAt('b\uf8ff').on('child_added', (snapshot) => { console.log(snapshot.key); });
Python
ref = db.reference('dinosaurs') snapshot = ref.order_by_key().start_at('b').end_at(u'b\uf8ff').get() for key in snapshot: print(key)
Go
ref := client.NewRef("dinosaurs") results, err := ref.OrderByKey().StartAt("b").EndAt("b\uf8ff").GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } for _, r := range results { fmt.Println(r.Key()) }
השיטה equalTo()
מאפשרת לסנן על סמך התאמות מדויקות. בדומה לשאילתות אחרות של טווחים, היא תופעל לכל צומת צאצא תואם. לדוגמה, אפשר להשתמש בשאילתה הבאה כדי למצוא את כל הדינוזאורים שגובהם 25 מטר:
Java
dinosaursRef.orderByChild("height").equalTo(25).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) { System.out.println(dataSnapshot.getKey()); } // ... });
Node.js
const ref = db.ref('dinosaurs'); ref.orderByChild('height').equalTo(25).on('child_added', (snapshot) => { console.log(snapshot.key); });
Python
ref = db.reference('dinosaurs') snapshot = ref.order_by_child('height').equal_to(25).get() for key in snapshot: print(key)
Go
ref := client.NewRef("dinosaurs") results, err := ref.OrderByChild("height").EqualTo(25).GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } for _, r := range results { fmt.Println(r.Key()) }
שאילתות טווח הן גם שימושיות כשצריך לפצל את הנתונים לדפים.
סיכום של כל המידע
אפשר לשלב את כל השיטות האלה כדי ליצור שאילתות מורכבות. לדוגמה, אפשר למצוא את שם הדינוזאור שקצת קצר יותר מהסטגוזאורוס:
Java
dinosaursRef.child("stegosaurus").child("height").addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot stegoHeightSnapshot) { Integer favoriteDinoHeight = stegoHeightSnapshot.getValue(Integer.class); Query query = dinosaursRef.orderByChild("height").endAt(favoriteDinoHeight).limitToLast(2); query.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // Data is ordered by increasing height, so we want the first entry DataSnapshot firstChild = dataSnapshot.getChildren().iterator().next(); System.out.println("The dinosaur just shorter than the stegosaurus is: " + firstChild.getKey()); } @Override public void onCancelled(DatabaseError databaseError) { // ... } }); } @Override public void onCancelled(DatabaseError databaseError) { // ... } });
Node.js
const ref = db.ref('dinosaurs'); ref.child('stegosaurus').child('height').on('value', (stegosaurusHeightSnapshot) => { const favoriteDinoHeight = stegosaurusHeightSnapshot.val(); const queryRef = ref.orderByChild('height').endAt(favoriteDinoHeight).limitToLast(2); queryRef.on('value', (querySnapshot) => { if (querySnapshot.numChildren() === 2) { // Data is ordered by increasing height, so we want the first entry querySnapshot.forEach((dinoSnapshot) => { console.log('The dinosaur just shorter than the stegasaurus is ' + dinoSnapshot.key); // Returning true means that we will only loop through the forEach() one time return true; }); } else { console.log('The stegosaurus is the shortest dino'); } }); });
Python
ref = db.reference('dinosaurs') favotire_dino_height = ref.child('stegosaurus').child('height').get() query = ref.order_by_child('height').end_at(favotire_dino_height).limit_to_last(2) snapshot = query.get() if len(snapshot) == 2: # Data is ordered by increasing height, so we want the first entry. # Second entry is stegosarus. for key in snapshot: print('The dinosaur just shorter than the stegosaurus is {0}'.format(key)) return else: print('The stegosaurus is the shortest dino')
Go
ref := client.NewRef("dinosaurs") var favDinoHeight int if err := ref.Child("stegosaurus").Child("height").Get(ctx, &favDinoHeight); err != nil { log.Fatalln("Error querying database:", err) } query := ref.OrderByChild("height").EndAt(favDinoHeight).LimitToLast(2) results, err := query.GetOrdered(ctx) if err != nil { log.Fatalln("Error querying database:", err) } if len(results) == 2 { // Data is ordered by increasing height, so we want the first entry. // Second entry is stegosarus. fmt.Printf("The dinosaur just shorter than the stegosaurus is %s\n", results[0].Key()) } else { fmt.Println("The stegosaurus is the shortest dino") }
סדר הנתונים
בקטע הזה מוסבר איך הנתונים ממוינים כשמשתמשים בכל אחת מארבע פונקציות המיון.
orderByChild
כשמשתמשים ב-orderByChild()
, הנתונים שמכילים את מפתח הצאצא שצוין ממוינים באופן הבא:
- צאצאים עם ערך
null
למפתח הצאצא שצוין מופיעים קודם. - לאחר מכן מופיעים צאצאים עם הערך
false
למפתח הצאצא שצוין. אם ליותר מילד אחד יש ערך שלfalse
, הם ממוינים לפי סדר אלפביתי לפי מפתח. - לאחר מכן מופיעים צאצאים עם הערך
true
למפתח הצאצא שצוין. אם ליותר מילד אחד יש ערך שלtrue
, הם ממוינים לפי מפתח אלפביתי. - לאחר מכן מופיעים צאצאים עם ערך מספרי, שממוינים בסדר עולה. אם לכמה צאצאים יש אותו ערך מספרי בצומת הצאצא שצוין, הם ממוינים לפי מפתח.
- מחרוזות מופיעות אחרי מספרים וממוינות לפי סדר אלפביתי עולה. אם לכמה צאצאים יש אותו ערך בצומת הצאצא שצוין, הם ממוינים לפי מפתח אלפביתי.
- האובייקטים מופיעים בסוף וממוינים לפי מפתח מילוני בסדר עולה.
orderByKey
כשמשתמשים ב-orderByKey()
כדי למיין את הנתונים, הנתונים מוחזרים בסדר עולה לפי מפתח, באופן הבא. חשוב לזכור שמפתחות יכולים להיות רק מחרוזות.
- צאצאים עם מפתח שאפשר לנתח כמספר שלם של 32 ביט מופיעים קודם, וממוינים בסדר עולה.
- לאחר מכן מופיעים צאצאים עם ערך מחרוזת כמפתח, שממוינים לפי סדר אלפביתי עולה.
orderByValue
כשמשתמשים ב-orderByValue()
, הצאצאים ממוינים לפי הערך שלהם. קריטריונים הסדר זהים לקריטריונים של orderByChild()
, חוץ מהעובדה שהערך של הצומת משמש במקום הערך של מפתח צאצא שצוין.