במסמך הזה מוסבר איך לאחזר נתונים ממסד נתונים, איך הנתונים מסודרים ואיך לבצע שאילתות פשוטות על הנתונים. אחזור נתונים ב-Admin SDK מיושם בצורה שונה מעט בשפות תכנות שונות.
- מאזינים אסינכרוניים: נתונים שמאוחסנים ב-Firebase Realtime Database מאוחזרים על ידי צירוף מאזין אסינכרוני להפניה למסד נתונים. המאזין מופעל פעם אחת עבור המצב הראשוני של הנתונים, ושוב בכל פעם שהנתונים משתנים. יכול להיות שמאזין לאירועים יקבל כמה סוגים שונים של אירועים. אפשר לאחזר נתונים בשיטה הזו באמצעות Java, Node.js ו-Python Admin SDKs.
- חסימת קריאות: נתונים שמאוחסנים ב-Firebase Realtime Database מאוחזרים על ידי הפעלה של שיטת חסימה בהפניה למסד נתונים, שמחזירה את הנתונים שמאוחסנים בהפניה. כל קריאה לשיטה היא פעולה חד-פעמית. כלומר, ערכת ה-SDK לא רושמת קריאות חוזרות (callback) שמאזינות לעדכוני נתונים עתידיים. המודל הזה של אחזור נתונים נתמך ב-Python וב-Go Admin SDKs.
תחילת העבודה
נחזור לדוגמה של הבלוג מהמאמר הקודם כדי להבין איך לקרוא נתונים ממסד נתונים של 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, הפונקציה של ה-listener נקראת בכל פעם שמוסיפים נתונים חדשים להפניה למסד הנתונים, ואין צורך לכתוב קוד נוסף כדי שזה יקרה.
ב-Java וב-Node.js, פונקציית הקריאה החוזרת מקבלת DataSnapshot
, שהוא קובץ snapshot של הנתונים. תמונת מצב היא תמונה של הנתונים בהפניה מסוימת למסד נתונים בנקודת זמן מסוימת. קריאה ל-val()
/ getValue()
בתמונת מצב מחזירה ייצוג אובייקט של הנתונים בשפה ספציפית. אם אין נתונים במיקום ההפניה, הערך של התמונה הוא null
. השיטה get()
ב-Python מחזירה ישירות ייצוג של הנתונים ב-Python. הפונקציה Get()
ב-Go מבטלת את ה-marshaling של הנתונים למבנה נתונים נתון.
שימו לב שבחרנו להשתמש בסוג האירוע value
בדוגמה שלמעלה. האירוע הזה קורא את כל התוכן של הפניה למסד נתונים של Firebase, גם אם רק חלק אחד מהנתונים השתנה. value
הוא אחד מחמשת סוגי האירועים השונים שמופיעים בהמשך, שאפשר להשתמש בהם כדי לקרוא נתונים ממסד הנתונים.
קריאת סוגי אירועים ב-Java וב-Node.js
ערך
האירוע value
משמש לקריאת תמונה סטטית של התוכן בנתיב מסוים במסד הנתונים, כפי שהיה בזמן אירוע הקריאה. הוא מופעל פעם אחת עם הנתונים הראשוניים, ועוד פעם בכל פעם שהנתונים משתנים. החזרת האירוע (callback) מעבירה תמונת מצב שמכילה את כל הנתונים במיקום הזה, כולל נתוני צאצא. בדוגמת הקוד שלמעלה, 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); });
בדוגמה הזו, התמונה תכיל אובייקט עם פוסט בבלוג. ה-SDK ממיר את הפוסטים לאובייקטים על ידי אחזור הערך, ולכן יש לכם גישה למאפיינים של מחבר הפוסט ושם הפריט על ידי קריאה ל-author
ול-title
בהתאמה. יש לכם גם גישה למזהה הפוסט הקודם מהארגומנט השני prevChildKey
.
החשבון של הילד או הילדה השתנה
האירוע child_changed
מופעל בכל פעם שמשנים צומת צאצא. השינויים האלה כוללים שינויים בצאצאים של צומת הילד. בדרך כלל משתמשים בו בשילוב עם child_added
ו-child_removed
כדי להגיב לשינויים ברשימת פריטים. תמונת המצב שמועברת לקריאה החוזרת של האירוע מכילה את הנתונים המעודכנים של הרכיב הצאצא.
אפשר להשתמש ב-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
. התמונה שמועברת לקריאה החוזרת של האירוע מכילה את הנתונים של צאצא שהוסר.
בדוגמה של הבלוג, אפשר להשתמש בפקודה 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 מספק כמה הבטחות חשובות לגבי אירועים:
ערבויות לאירועים במסד הנתונים |
---|
האירועים תמיד יופעלו כשיהיו שינויים במצב המקומי. |
בסופו של דבר, האירועים תמיד ישקפו את המצב הנכון של הנתונים, גם במקרים שבהם פעולות מקומיות או תזמון גורמים להבדלים זמניים, כמו במקרה של אובדן זמני של חיבור לרשת. |
פעולות כתיבה מלקוח יחיד תמיד ייכתבו בשרת וישודרו למשתמשים אחרים לפי הסדר. |
הפעלת אירועי ערך מתבצעת תמיד אחרונה, ויש בהם עדכונים מכל האירועים האחרים שהתרחשו לפני צילום התמונה. |
מכיוון שאירועי ערך מופעלים תמיד אחרונים, הדוגמה הבאה תמיד תפעל:
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()
, צריך להעביר אותו כשמנתקים את הקריאה החוזרת:
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();
קריאת נתונים פעם אחת
במקרים מסוימים, יכול להיות שיהיה שימושי לקרוא לפונקציית callback פעם אחת ואז להסיר אותה באופן מיידי. כדי להקל על התהליך, יצרנו פונקציית עזר:
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(f'{key} was {val} meters tall')
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(f'{key} was {val} meters tall')
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(f'The {key} dinosaur\'s score is {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 מההודעות האלה. אם אתם משתמשים ב-limitToFirst()
, מוצגות 100 ההודעות הראשונות לפי הסדר, ואם אתם משתמשים ב-limitToLast()
, מוצגות 100 ההודעות האחרונות לפי הסדר. כשהפריטים משתנים, תקבלו אירועי 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()) }
הפונקציה child_added
callback מופעלת בדיוק פעמיים, אלא אם יש פחות משני דינוזאורים שמאוחסנים במסד הנתונים. הפונקציה תופעל גם לכל דינוזאור חדש וכבד יותר שיתווסף למסד הנתונים.
ב-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
callback מופעלת בדיוק פעמיים, אלא אם יש פחות משני דינוזאורים שמאוחסנים במסד הנתונים. הפונקציה תופעל שוב גם אם אחד משני הדינוזאורים הראשונים יוסר ממסד הנתונים, כי עכשיו דינוזאור חדש יהיה השני הכי קצר. ב-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(f'The {key} dinosaur\'s score is {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()
כדי להגביל את שני הקצוות של השאילתה. בדוגמה הבאה מוצגים כל הדינוזאורים שהשם שלהם מתחיל באות 'ב':
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('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(f'The dinosaur just shorter than the stegosaurus is {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()
, אלא שהערך של הצומת משמש במקום הערך של מפתח צאצא שצוין.