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

يتناول هذا المستند أساسيات استرداد بيانات قاعدة البيانات، وكيفية ترتيب البيانات، وكيفية إجراء طلبات بحث بسيطة على البيانات. يتم تنفيذ استرداد البيانات في "SDK للمشرف" بشكل مختلف قليلاً في لغات البرمجة المختلفة.

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

البدء

لنراجع مثال التدوين من المقالة السابقة لفهم كيفية قراءة البيانات من قاعدة بيانات Firebase. وتذكَّر أنّ مشاركات المدوّنات في نموذج التطبيق يتم تخزينها في عنوان URL لقاعدة البيانات https://docs-examples.firebaseio.com/server/save-data/fireblog/حف.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() في بايثون تمثيل بايثون للبيانات مباشرةً. تفسر الدالة 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('{0} was {1} meters tall'.format(key, val))
Go

// Dinosaur is a json-serializable type.
type Dinosaur struct {
	Height int `json:"height"`
	Width  int `json:"width"`
}

ref := client.NewRef("dinosaurs")

results, err := ref.OrderByChild("height").GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	var d Dinosaur
	if err := r.Unmarshal(&d); err != nil {
		log.Fatalln("Error unmarshaling result:", err)
	}
	fmt.Printf("%s was %d meteres tall", r.Key(), d.Height)
}

أما أي عقدة لا تحتوي على المفتاح الفرعي الذي نبحث عنه، فيتم ترتيبها بقيمة null، ما يعني أنّها ستأتي في المقام الأول بالترتيب. لمعرفة التفاصيل حول كيفية ترتيب البيانات، يمكنك الاطّلاع على قسم "كيفية ترتيب البيانات".

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

{
  "lambeosaurus": {
    "dimensions": {
      "height" : 2.1,
      "length" : 12.5,
      "weight": 5000
    }
  },
  "stegosaurus": {
    "dimensions": {
      "height" : 4,
      "length" : 9,
      "weight" : 2500
    }
  }
}

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

Java
dinosaursRef.orderByChild("dimensions/height").addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    // ...
  }

  // ...
});
Node.js
const ref = db.ref('dinosaurs');
ref.orderByChild('dimensions/height').on('child_added', (snapshot) => {
  console.log(snapshot.key + ' was ' + snapshot.val().height + ' meters tall');
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('dimensions/height').get()
for key, val in snapshot.items():
    print('{0} was {1} meters tall'.format(key, val))
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByChild("dimensions/height").GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	var d Dinosaur
	if err := r.Unmarshal(&d); err != nil {
		log.Fatalln("Error unmarshaling result:", err)
	}
	fmt.Printf("%s was %d meteres tall", r.Key(), d.Height)
}

يمكن ترتيب طلبات البحث حسب مفتاح واحد فقط في كل مرة. يؤدي طلب الرمز orderByChild() عدة مرات في طلب البحث نفسه إلى ظهور خطأ.

الترتيب حسب المفتاح

يمكنك أيضًا ترتيب العُقد حسب مفاتيحها باستخدام طريقة orderByKey(). يقرأ المثال التالي جميع الديناصورات بالترتيب الأبجدي:

Java
dinosaursRef.orderByKey().addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
var ref = db.ref('dinosaurs');
ref.orderByKey().on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().get()
print(snapshot)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByKey().GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
snapshot := make([]Dinosaur, len(results))
for i, r := range results {
	var d Dinosaur
	if err := r.Unmarshal(&d); err != nil {
		log.Fatalln("Error unmarshaling result:", err)
	}
	snapshot[i] = d
}
fmt.Println(snapshot)

الترتيب حسب القيمة

ويمكنك ترتيب العُقد حسب قيمة مفاتيحها الفرعية باستخدام طريقة orderByValue(). لنفترض أن هناك منافسة بين الديناصورات وأنك تتبع نتائجها بالتنسيق التالي:

{
  "scores": {
    "bruhathkayosaurus" : 55,
    "lambeosaurus" : 21,
    "linhenykus" : 80,
    "pterodactyl" : 93,
    "stegosaurus" : 5,
    "triceratops" : 22
  }
}

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

Java
DatabaseReference scoresRef = database.getReference("scores");
scoresRef.orderByValue().addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println("The " + dataSnapshot.getKey() + " score is " + dataSnapshot.getValue());
  }

  // ...
});
Node.js
const scoresRef = db.ref('scores');
scoresRef.orderByValue().on('value', (snapshot) => {
  snapshot.forEach((data) => {
    console.log('The ' + data.key + ' dinosaur\'s score is ' + data.val());
  });
});
Python
ref = db.reference('scores')
snapshot = ref.order_by_value().get()
for key, val in snapshot.items():
    print('The {0} dinosaur\'s score is {1}'.format(key, val))
Go
ref := client.NewRef("scores")

results, err := ref.OrderByValue().GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	var score int
	if err := r.Unmarshal(&score); err != nil {
		log.Fatalln("Error unmarshaling result:", err)
	}
	fmt.Printf("The %s dinosaur's score is %d\n", r.Key(), score)
}

راجِع القسم كيفية ترتيب البيانات للحصول على شرح حول كيفية ترتيب القيم null والقيم المنطقية والسلسلة والكائن عند استخدام orderByValue().

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

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

تحديد طلبات البحث

يتم استخدام طلبَي البحث 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 مرتين بالضبط، ما لم يكن هناك أقل من اثنين من الديناصورات مخزّنة في قاعدة البيانات. كما سيتم تنشيطه أيضًا مع كل ديناصور جديد أثقل تتم إضافته إلى قاعدة البيانات. في بايثون، يعرض الاستعلام مباشرةً 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 مرتين بالضبط، ما لم يكن هناك أقل من ديناصورين مخزّنين في قاعدة البيانات. كما سيتم تنشيطه مرة أخرى إذا تمت إزالة أحد أول ديناصورات من قاعدة البيانات، حيث سيكون الديناصور الجديد الآن ثاني أقصر ديناصور. في بايثون، يعرض الاستعلام مباشرةً OrderedDict الذي يحتوي على أقصر ديناصورات.

يمكنك أيضًا إجراء طلبات بحث محدَّدة باستخدام orderByValue(). إذا كنت تريد إنشاء لوحة صدارة تتضمن أعلى 3 ديناصورات في ألعاب الديناصورات، يمكنك إجراء ما يلي:

Java
scoresRef.orderByValue().limitToFirst(3).addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println("The " + dataSnapshot.getKey() + " score is " + dataSnapshot.getValue());
  }

  // ...
});
Node.js
const scoresRef = db.ref('scores');
scoresRef.orderByValue().limitToLast(3).on('value', (snapshot)  =>{
  snapshot.forEach((data) => {
    console.log('The ' + data.key + ' dinosaur\'s score is ' + data.val());
  });
});
Python
scores_ref = db.reference('scores')
snapshot = scores_ref.order_by_value().limit_to_last(3).get()
for key, val in snapshot.items():
    print('The {0} dinosaur\'s score is {1}'.format(key, val))
Go
ref := client.NewRef("scores")

results, err := ref.OrderByValue().LimitToLast(3).GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	var score int
	if err := r.Unmarshal(&score); err != nil {
		log.Fatalln("Error unmarshaling result:", err)
	}
	fmt.Printf("The %s dinosaur's score is %d\n", r.Key(), score)
}

طلبات البحث عن النطاق

يتيح لك استخدام startAt() وendAt() وequalTo() اختيار نقاط بداية ونهاية عشوائية لطلبات البحث. على سبيل المثال، إذا أردت العثور على جميع الديناصورات التي يبلغ طولها ثلاثة أمتار على الأقل، يمكنك الجمع بين orderByChild() وstartAt():

Java
dinosaursRef.orderByChild("height").startAt(3).addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
const ref = db.ref('dinosaurs');
ref.orderByChild('height').startAt(3).on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').start_at(3).get()
for key in snapshot:
    print(key)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByChild("height").StartAt(3).GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	fmt.Println(r.Key())
}

يمكنك استخدام endAt() للعثور على جميع الديناصورات التي تأتي أسماؤها قبل تيروداكتيل من ناحية جمالية:

Java
dinosaursRef.orderByKey().endAt("pterodactyl").addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
const ref = db.ref('dinosaurs');
ref.orderByKey().endAt('pterodactyl').on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().end_at('pterodactyl').get()
for key in snapshot:
    print(key)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByKey().EndAt("pterodactyl").GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	fmt.Println(r.Key())
}

يمكنك الجمع بين startAt() وendAt() لتحديد طرفَي طلب البحث. يظهر في المثال التالي جميع الديناصورات التي يبدأ اسمها بالحرف "b":

Java
dinosaursRef.orderByKey().startAt("b").endAt("b\uf8ff").addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
var ref = db.ref('dinosaurs');
ref.orderByKey().startAt('b').endAt('b\uf8ff').on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().start_at('b').end_at(u'b\uf8ff').get()
for key in snapshot:
    print(key)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByKey().StartAt("b").EndAt("b\uf8ff").GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	fmt.Println(r.Key())
}

تسمح لك طريقة equalTo() بإجراء فلترة استنادًا إلى المطابقات التامة. وكما هي الحال مع طلبات بحث النطاق الأخرى، سيتم تنشيطها لكل عقدة فرعية مطابقة. على سبيل المثال، يمكنك استخدام الاستعلام التالي للعثور على جميع الديناصورات التي يبلغ ارتفاعها 25 متر:

Java
dinosaursRef.orderByChild("height").equalTo(25).addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
const ref = db.ref('dinosaurs');
ref.orderByChild('height').equalTo(25).on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').equal_to(25).get()
for key in snapshot:
    print(key)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByChild("height").EqualTo(25).GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	fmt.Println(r.Key())
}

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

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

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

Java
dinosaursRef.child("stegosaurus").child("height").addValueEventListener(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot stegoHeightSnapshot) {
    Integer favoriteDinoHeight = stegoHeightSnapshot.getValue(Integer.class);
    Query query = dinosaursRef.orderByChild("height").endAt(favoriteDinoHeight).limitToLast(2);
    query.addValueEventListener(new ValueEventListener() {
      @Override
      public void onDataChange(DataSnapshot dataSnapshot) {
        // Data is ordered by increasing height, so we want the first entry
        DataSnapshot firstChild = dataSnapshot.getChildren().iterator().next();
        System.out.println("The dinosaur just shorter than the stegosaurus is: " + firstChild.getKey());
      }

      @Override
      public void onCancelled(DatabaseError databaseError) {
        // ...
      }
    });
  }

  @Override
  public void onCancelled(DatabaseError databaseError) {
    // ...
  }
});
Node.js
  const ref = db.ref('dinosaurs');
  ref.child('stegosaurus').child('height').on('value', (stegosaurusHeightSnapshot) => {
    const favoriteDinoHeight = stegosaurusHeightSnapshot.val();

    const queryRef = ref.orderByChild('height').endAt(favoriteDinoHeight).limitToLast(2);
    queryRef.on('value', (querySnapshot) => {
      if (querySnapshot.numChildren() === 2) {
        // Data is ordered by increasing height, so we want the first entry
        querySnapshot.forEach((dinoSnapshot) => {
          console.log('The dinosaur just shorter than the stegasaurus is ' + dinoSnapshot.key);

          // Returning true means that we will only loop through the forEach() one time
          return true;
        });
      } else {
        console.log('The stegosaurus is the shortest dino');
      }
    });
});
Python
ref = db.reference('dinosaurs')
favotire_dino_height = ref.child('stegosaurus').child('height').get()
query = ref.order_by_child('height').end_at(favotire_dino_height).limit_to_last(2)
snapshot = query.get()
if len(snapshot) == 2:
    # Data is ordered by increasing height, so we want the first entry.
    # Second entry is stegosarus.
    for key in snapshot:
        print('The dinosaur just shorter than the stegosaurus is {0}'.format(key))
        return
else:
    print('The stegosaurus is the shortest dino')
Go
ref := client.NewRef("dinosaurs")

var favDinoHeight int
if err := ref.Child("stegosaurus").Child("height").Get(ctx, &favDinoHeight); err != nil {
	log.Fatalln("Error querying database:", err)
}

query := ref.OrderByChild("height").EndAt(favDinoHeight).LimitToLast(2)
results, err := query.GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
if len(results) == 2 {
	// Data is ordered by increasing height, so we want the first entry.
	// Second entry is stegosarus.
	fmt.Printf("The dinosaur just shorter than the stegosaurus is %s\n", results[0].Key())
} else {
	fmt.Println("The stegosaurus is the shortest dino")
}

كيف يتم ترتيب البيانات

يشرح هذا القسم كيف يتم ترتيب بياناتك عند استخدام كل من دوال الترتيب الأربع.

orderByChild

عند استخدام orderByChild()، يتم ترتيب البيانات التي تحتوي على المفتاح الفرعي المحدّد على النحو التالي:

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

مفتاح الطلب

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

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

قيمة طلب الشراء

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