检索数据

本文档将介绍检索数据库数据的基础知识、数据的排序方式,以及如何对数据执行简单的查询。在不同的编程语言中,Admin SDK 中数据检索的实现方式略有不同。

  1. 异步监听器:通过将异步监听器附加到数据库引用来检索 Firebase Realtime Database 中存储的数据。该监听器会针对数据的初始状态触发一次,以后只要数据有更改就会再次触发。事件监听器可以接收几种不同类型的事件。Java 和 Node.js Admin SDK 均支持这种数据检索模式。
  2. 阻塞读取:通过调用数据库引用中的阻塞方法来检索 Firebase Realtime Database 中存储的数据,该方法会返回存储在此引用中的数据。每次方法调用都是一次性操作。这意味着 SDK 不会注册任何监听后续数据更新的回调函数。Python Admin SDK 和 Go Admin SDK 支持这种数据检索模式。

开始使用

下面,我们来回顾上一篇文章中的博客示例,了解如何从 Firebase 数据库中读取数据。根据该文章中的说明,示例应用中的博文存储在数据库网址 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。Python 中的 get() 方法会直接返回数据的 Python 表示形式。Go 中的 Get() 函数会将数据解组为指定的数据结构。

请注意,我们在以上示例中使用了 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 通过检索值将博文转换为对象,因此您可以分别通过调用 authortitle 来访问博文的作者属性和标题属性。您还可以通过第二个 prevChildKey 参数获得上一篇博文的 ID。

已更改的子项目

child_changed 事件会在每次修改子节点时触发。包括对子节点的后代所做的任何修改。它通常与 child_addedchild_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_addedchild_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)
}

请参阅数据的排序方式部分,了解在使用 orderByValue() 时如何对 null、布尔值、字符串和对象值进行排序。

复杂的查询

现在您已经清楚数据的排序方式,您可以使用下面介绍的限制范围方法构建更复杂的查询。

限制查询

limitToFirst()limitToLast() 查询用于为给定的回调函数设置要同步的最大子项目数。如果您将数量上限设置为 100,则最初只能接收最多 100 个 child_added 事件。如果数据库中只存储了不到 100 条消息,那么每条消息都可以触发一个 child_added 事件。但是,如果有超过 100 条消息,那么这些消息中只有 100 条可以触发 child_added 事件。如果使用的是 limitToFirst(),那么这 100 条消息即是排在最前面的 100 条;如果使用的是 limitToLast(),那么就是排在最后面的 100 条消息。随着项发生更改,对于落入查询范围的项,您将收到 child_added 事件;对于不再属于查询范围的项,您将收到 child_removed 事件,因此总数始终保持为 100。

使用恐龙信息数据库和 orderByChild(),您可以找到最重的两种恐龙:

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

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

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

除非数据库中存储的恐龙少于两种,否则 child_added 回调函数将正好触发两次。每次将更重的新恐龙添加到数据库中时,也会触发该回调函数。在 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() 进行限制查询。如果您要利用恐龙体育比赛中得分排在前三名的恐龙创建一个排行榜,则可执行以下操作:

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() 找出名称按字典顺序排在 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() 来限制查询范围的两端。以下示例可找出名称以字母“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())
}

如果需要将数据分页,范围查询也非常有用。

综合应用

您可以结合使用所有这些方法来创建复杂的查询。例如,您可以查找身高正好排在 Stegosaurus(剑龙)之后的恐龙的名称:

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

使用 orderByKey() 对数据进行排序时,系统会按如下方式根据键以升序返回数据。请注意,键只能是字符串。

  1. 键可以解析为 32 位整数的子项目显示在最前,按升序排序。
  2. 以字符串值作为键的子项目紧随其后,按字典顺序以升序排列。

orderByValue

使用 orderByValue() 时,系统将按子项本身的值对其进行排序。排序标准与 orderByChild() 中相同,但这里使用的是节点本身的值而非指定子键的值。