يتناول هذا المستند أساسيات استرداد بيانات قاعدة البيانات وكيفية ترتيب البيانات وكيفية تنفيذ طلبات بحث بسيطة على البيانات. يتم تنفيذ عملية استرداد البيانات في حزمة تطوير البرامج (SDK) الخاصة بالمشرف بشكل مختلف قليلاً في لغات البرمجة المختلفة.
- المستمعون غير المتزامنون: يتم استرداد البيانات المخزّنة في Firebase Realtime Database من خلال ربط مستمع غير متزامن بمرجع قاعدة بيانات. يتم تشغيل أداة الاستماع مرة واحدة للحالة الأولية للبيانات، ومرة أخرى كلما تغيرت البيانات. قد يتلقّى معالج الأحداث عدة أنواع مختلفة من الأحداث. يتوفّر وضع استرداد البيانات هذا في حِزم تطوير البرامج (SDK) الخاصة بالمشرفين في Java وNode.js وPython.
- عمليات القراءة الحظرية: يتم استرداد البيانات المخزّنة في Firebase Realtime Database من خلال استدعاء طريقة حظرية في مرجع قاعدة البيانات، والتي تعرض البيانات المخزّنة في المرجع. كل استدعاء للطريقة هو عملية لمرة واحدة. وهذا يعني أنّ حزمة SDK لا تسجّل أيّ عمليات ردّ الاتصال التي تستمع إلى تحديثات البيانات اللاحقة. يتوفّر نموذج استرداد البيانات هذا في حِزم SDK للمشرفين في Python وGo.
البدء
لنراجع مثال التدوين من المقالة السابقة لفهم كيفية قراءة البيانات من قاعدة بيانات 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
، وهي لقطة للبيانات. اللقطة هي صورة للبيانات في مرجع قاعدة بيانات معيّن في نقطة زمنية واحدة. يؤدي استدعاء val()
/ getValue()
على لقطة إلى عرض تمثيل خاص باللغة للبيانات. إذا لم تتوفّر بيانات في الموقع الجغرافي للمرجع، تكون قيمة اللقطة null
. تعرض الطريقة get()
في Python تمثيلاً للبيانات بلغة Python مباشرةً. تعمل الدالة Get()
في Go على إلغاء تسلسل البيانات إلى بنية بيانات معيّنة.
لاحظ أنّنا استخدمنا نوع الحدث value
في المثال أعلاه، والذي يقرأ المحتوى الكامل لمرجع قاعدة بيانات Firebase، حتى إذا تغيّرت قطعة واحدة فقط من البيانات. value
هو أحد أنواع الأحداث الخمسة المختلفة المدرَجة أدناه التي يمكنك استخدامها لقراءة البيانات من قاعدة البيانات.
قراءة أنواع الأحداث في Java وNode.js
القيمة
يُستخدَم الحدث value
لقراءة لقطة ثابتة للمحتوى في مسار قاعدة بيانات معيّن، كما كان موجودًا في وقت حدث القراءة. يتم تشغيله مرة واحدة مع البيانات الأولية، ثم مرة أخرى في كل مرة تتغير فيها البيانات. يتم تمرير لقطة تحتوي على جميع البيانات في هذا الموقع، بما في ذلك بيانات العناصر التابعة، إلى دالة معاودة الاتصال الخاصة بالحدث. في مثال الرمز البرمجي أعلاه، عرضت 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); });
في هذا المثال، ستتضمّن اللقطة عنصرًا يتضمّن مشاركة مدوّنة فردية. بما أنّ حزمة تطوير البرامج (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();
قراءة البيانات مرة واحدة
في بعض الحالات، قد يكون من المفيد استدعاء دالة رد الاتصال مرة واحدة ثم إزالتها على الفور. لقد أنشأنا دالة مساعدة لتسهيل هذه العملية:
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 رسالة منها. هذه هي أول 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()) }
يتم تشغيل دالة الاستدعاء 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(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()
، باستثناء أنّه يتم استخدام قيمة العقدة بدلاً من قيمة مفتاح ثانوي محدّد.