جارٍ استرداد البيانات

يتناول هذا المستند أساسيات استرداد بيانات قاعدة البيانات وكيفية ترتيب البيانات وكيفية تنفيذ طلبات بحث بسيطة عن البيانات. يتم تنفيذ استرداد البيانات في حزمة Admin SDK بشكلٍ مختلف قليلاً في لغات برمجة مختلفة.

  1. المستمعون غير المتزامنين: يتم استرداد البيانات المخزّنة في Firebase Realtime Database من خلال إرفاق مستمع غير متزامن بمرجع قاعدة بيانات. يتم تفعيل المستمع مرة واحدة للحالة الأولية للبيانات مرة أخرى في أي وقت تتغيّر فيه البيانات. قد يتلقّى مستمع الأحداث عدة أنواع مختلفة من الأحداث. يتوفّر وضع استرداد البيانات هذا في حِزم تطوير البرامج (SDK) الخاصة بالمشرفين في Java وNode.js وPython.
  2. عمليات القراءة المحظورة: يتم استرداد البيانات المخزّنة في Firebase Realtime Database من خلال استدعاء طريقة حظر في مرجع قاعدة بيانات ، ما يؤدي إلى عرض البيانات المخزّنة في المرجع. كلّ استدعاء طريقة هو عملية لمرّة واحدة. وهذا يعني أنّ حزمة SDK لا تسجّل أيّ طلبات استدعاء تستمع إلى تعديلات البيانات اللاحقة. يتوفّر نموذج استرداد البيانات هذا في حِزم تطوير البرامج (SDK) للمشرفين في Python وGo.

البدء

لنطّلِع مجددًا على مثال التدوين من المقالة السابقة لنفهم كيفية قراءة البيانات من قاعدة بيانات Firebase. تذكَّر أنّ المشاركات في المدوّنة في مثال التطبيق يتم تخزينها على عنوان URL لقاعدة البيانات https://docs-examples.firebaseio.com/server/saving-data/fireblog/posts.json. لقراءة بيانات مشاركاتك، يمكنك إجراء ما يلي:

جافا
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())
انتقال
// 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:

جافا
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 لقراءة البيانات المعدَّلة في مشاركات المدونات عند تعديلها:

جافا
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 لتسجيل إشعار بشأن المشاركة المحذوفة في وحدة التحكّم:

جافا
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 عدة ضمانات مهمة بشأن الأحداث:

ضمانات أحداث قاعدة البيانات
سيتم تشغيل الأحداث دائمًا عند تغيير الحالة المحلية.
ستعكس الأحداث دائمًا الحالة الصحيحة للبيانات، حتى في الحالات التي تؤدي فيها العمليات المحلية أو التوقيت إلى حدوث اختلافات مؤقتة، مثل فقدان الاتصال بالشبكة مؤقتًا.
ستتم دائمًا كتابة عمليات الكتابة من عميل واحد على الخادم وبثها إلى المستخدمين الآخرين بالترتيب.
يتم دائمًا بدء أحداث القيمة في آخر وقت، ويتم ضمان أن تحتوي على تعديلات من أي أحداث أخرى حدثت قبل أخذ هذه اللقطة.

بما أنّ أحداث القيم يتم تشغيلها دائمًا في آخر خطوة، سيعمل المثال التالي دائمًا:

جافا
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);
});

فصل طلبات معاودة الاتصال

تتم إزالة وظائف ردّ الاتصال من خلال تحديد نوع الحدث ودالة ردّ الاتصال المطلوب إزالتها، على النحو التالي:

جافا
// Create and attach listener
ValueEventListener listener = new ValueEventListener() {
    // ...
};
ref.addValueEventListener(listener);

// Remove listener
ref.removeEventListener(listener);
Node.js
ref.off('value', originalCallback);

إذا تمّ تمرير سياق نطاق إلى on()، يجب تمريره عند إلغاء ربط طلب الاستدعاء:

جافا
// Not applicable for Java
Node.js
ref.off('value', originalCallback, ctx);

إذا أردت إزالة جميع عمليات معاودة الاتصال في موقع جغرافي، يمكنك إجراء ما يلي:

جافا
// 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();

قراءة البيانات مرة واحدة

في بعض الحالات، قد يكون من المفيد استدعاء طلب معاودة الاتصال مرة واحدة ثم إزالته على الفور. لقد أنشأنا دالة مساعدة لتسهيل ذلك:

جافا
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())
انتقال
// 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(). على سبيل المثال، لقراءة جميع الديناصورات مرتّبة حسب الارتفاع، يمكنك تنفيذ ما يلي:

جافا
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))
انتقال
// 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
    }
  }
}

لطلب معلومات عن الارتفاع الآن، يمكنك استخدام المسار الكامل إلى الكائن بدلاً من مفتاح واحد:

جافا
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))
انتقال
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(). يقرأ المثال التالي جميع الديناصورات بترتيب أبجدي:

جافا
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)
انتقال
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
  }
}

لترتيب الديناصورات حسب نتيجتها، يمكنك إنشاء طلب البحث التالي:

جافا
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))
انتقال
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().

طلبات البحث المعقّدة

بعد أن أصبح من الواضح كيفية ترتيب بياناتك، يمكنك استخدام طريقتَي الحدّ أو النطاق الموضّحتَين أدناه لإنشاء طلبات بحث أكثر تعقيدًا.

طلبات البحث المحدود

يتم استخدام طلبَي البحث limitToFirst() وlimitToLast() لتحديد الحد الأقصى لعدد الأطفال المطلوب مزامنته لطلب استدعاء معيّن. إذا حدّدت الحدّ الأقصى على 100، لن تتلقّى في البداية سوى 100 حدث child_added كحدّ أقصى. إذا كان لديك أقل من 100 رسالة مخزّنة في قاعدة بياناتك، سيتمّ بدء حدث child_added لكل رسالة. ومع ذلك، إذا كان لديك أكثر من 100 رسالة، لن تتلقّى سوى حدث child_added لـ 100 من هذه الرسائل. هذه هي أوّل 100 رسالة مرتبة إذا كنت تستخدم limitToFirst() أو آخر 100 رسالة مرتبة إذا كنت تستخدم limitToLast(). مع تغيُّر العناصر، ستتلقّى child_added حدثًا للعناصر التي تدخل طلب البحث وchild_removed حدثًا للعناصر التي تغادره، بحيث يظلّ العدد الإجمالي 100.

باستخدام قاعدة بيانات حقائق الديناصورات وorderByChild()، يمكنك العثور على أثقل ديناصورين:

جافا
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)
انتقال
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():

جافا
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)
انتقال
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 ديناصورات حققت أعلى النقاط في لعبة Dino Sports، يمكنك اتّباع الخطوات التالية:

جافا
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))
انتقال
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():

جافا
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)
انتقال
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 أبجديًا:

جافا
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)
انتقال
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() للحد من كلا طرفَي طلب البحث. يعثر المثال التالي على جميع الديناصورات التي تبدأ أسماؤها بالحرف "ب":

جافا
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)
انتقال
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 مترًا:

جافا
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)
انتقال
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())
}

تكون طلبات البحث في النطاق مفيدة أيضًا عندما تحتاج إلى تقسيم بياناتك إلى صفحات.

خلاصة ما سبق ذكره

يمكنك الجمع بين كل هذه الأساليب لإنشاء طلبات بحث معقّدة. على سبيل المثال، يمكنك العثور على اسم الديناصور الأقصر من Stegosaurus:

جافا
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')
انتقال
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()، يتم ترتيب البيانات التي تحتوي على مفتاح فرعي محدّد على النحو التالي:

  1. تظهر أولاً القيم التي تحتوي على null لمفتاح الطفل المحدّد.
  2. تأتي بعد ذلك القيم التي تحتوي على false لمفتاح الطفل المحدّد. إذا كانت قيمة عناصر متعددة هي false، يتم ترتيبها ألفبائيًا حسب المفتاح.
  3. تأتي بعد ذلك القيم التي تحتوي على true لمفتاح الطفل المحدّد. إذا كانت قيمة عناصر فرعية متعددة هي true، يتم ترتيبها أبجديًا حسب المفتاح.
  4. تأتي العناصر الفرعية التي تحتوي على قيمة رقمية بعد ذلك، ويتم ترتيبها تصاعديًا. إذا كانت عدّة عناصر فرعية لها القيمة الرقمية نفسها للعقدة الفرعية المحدّدة، يتم ترتيبها حسب المفتاح.
  5. تأتي السلاسل بعد الأرقام، ويتم ترتيبها أبجديًا بترتيب تصاعدي. إذا كانت عدّة عناصر فرعية لها القيمة نفسها للعقدة الفرعية المحدّدة، يتم ترتيبها أبجديًا حسب المفتاح.
  6. تظهر العناصر في آخر القائمة، ويتم ترتيبها أبجديًا حسب المفتاح بترتيب تصاعدي.

orderByKey

عند استخدام orderByKey() لترتيب بياناتك، يتم عرض البيانات بترتيب تصاعدي حسب المفتاح على النحو التالي. يُرجى العِلم أنّ المفاتيح يمكن أن تكون سلاسل فقط.

  1. تظهر أولاً العناصر الفرعية التي تحتوي على مفتاح يمكن تحليله كعدد صحيح 32 بت، ويتم ترتيبها تصاعديًا.
  2. تأتي بعد ذلك العناصر الفرعية التي تحتوي على قيمة سلسلة كمفتاح لها، ويتم ترتيبها أبجديًا بترتيب تصاعدي.

orderByValue

عند استخدام orderByValue()، يتم ترتيب العناصر الفرعية حسب قيمتها. تكون معايير الترتيب هي نفسها في orderByChild()، باستثناء أنّه يتم استخدام قيمة العقدة بدلاً من قيمة مفتاح فرعي محدّد.