במסמך הזה נסביר את העקרונות הבסיסיים של אחזור נתוני מסד נתונים, את אופן הסדר של הנתונים ואת האופן שבו מבצעים שאילתות פשוטות על הנתונים. תהליך אחזור הנתונים ב-Admin SDK מיושם באופן מעט שונה בשפות תכנות שונות.
- מאזינים אסינכרונים: כדי לאחזר נתונים שמאוחסנים ב-Firebase Realtime Database, צריך לצרף מאזין אסינכרוני להפניה למסד נתונים. המאזין מופעל פעם אחת עבור המצב הראשוני של הנתונים, ופעם נוספת בכל פעם שהנתונים משתנים. יכול להיות שמאזין לאירועים יקבל כמה סוגים של אירועים. מצב אחזור הנתונים הזה נתמך ב-SDK לניהול של Java, Node.js ו-Python.
- קריאות חסימה: נתונים שמאוחסנים ב-Firebase Realtime Database מאוחזרים על ידי הפעלת שיטת חסימה על הפניה למסד נתונים, שמחזירה את הנתונים שמאוחסנים בהפניה. כל הפעלת method היא פעולה חד-פעמית. כלומר, ה-SDK לא רושם קריאות חזרה (callbacks) שמקשיבות לעדכוני נתונים נוספים. מודל אחזור הנתונים הזה נתמך ב-SDK של Python וב-SDK של Go Admin.
תחילת העבודה
נבחן שוב את הדוגמה לבלוג מהמאמר הקודם כדי להבין איך קוראים נתונים ממסד נתונים של 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
מופעלת פעם אחת לכל צאצא קיים ואז שוב בכל פעם שמתווסף צאצא חדש לנתיב שצוין. הקריאה החוזרת של האירוע מועברת בתמונת מצב שמכילה את הנתונים של הילד או הילדה החדשים. לצורך סדר, מועבר אליו גם ארגומנט שני שמכיל את המפתח של הצאצא הקודם.
אם רוצים לאחזר רק את הנתונים של כל פוסט חדש שנוסף לאפליקציית הבלוג, אפשר להשתמש ב-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); });
ניתוק קריאות חוזרות
כדי להסיר פונקציות קריאה חוזרת, מציינים את סוג האירוע ואת פונקציית הקריאה החוזרת שרוצים להסיר, כמו בדוגמה הבאה:
Java
// Create and attach listener ValueEventListener listener = new ValueEventListener() { // ... }; ref.addValueEventListener(listener); // Remove listener ref.removeEventListener(listener);
Node.js
ref.off('value', originalCallback);
אם העברת הקשר היקף ל-on()
, צריך להעביר אותו גם כשמנתקים את פונקציית ה-callback:
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()) }
הקריאה החוזרת 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()
כדי למצוא את כל הדינוזאורים שהשמות שלהם מופיעים לפני פטרודקטילוס
באופן מילוני:
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()
, חוץ מהעובדה שהערך של הצומת משמש במקום הערך של מפתח צאצא שצוין.