檢索數據

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

本文檔涵蓋檢索數據庫數據的基礎知識、數據的排序方式以及如何對數據執行簡單查詢。 Admin SDK 中的數據檢索在不同編程語言中的實現方式略有不同。

  1. 異步偵聽器:通過將異步偵聽器附加到數據庫引用來檢索存儲在 Firebase 實時數據庫中的數據。偵聽器會針對數據的初始狀態觸發一次,並在數據更改時再次觸發。一個事件偵聽器可能會接收幾種不同類型的事件。 Java、Node.js 和 Python Admin SDK 支持這種數據檢索模式。
  2. 阻塞讀取:通過在數據庫引用上調用阻塞方法來檢索存儲在 Firebase 實時數據庫中的數據,該方法返回存儲在引用中的數據。每個方法調用都是一次性操作。這意味著 SDK 不會註冊任何偵聽後續數據更新的回調。 Python 和 Go Admin SDK 支持這種數據檢索模型。

入門

讓我們重溫上一篇文章中的博客示例,了解如何從 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());
  }
});
節點.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 。 Python 中的get()方法直接返回數據的 Python 表示。 Go 中的Get()函數將數據解組到給定的數據結構中。

請注意,我們在上面的示例中使用了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) {}
});
節點.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來訪問帖子的 author 和 title 屬性。您還可以從第二個prevChildKey參數訪問上一個帖子 ID。

孩子變了

每當修改子節點時都會觸發child_changed事件。這包括對子節點後代的任何修改。它通常與child_addedchild_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) {}
});
節點.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將有關已刪除帖子的通知記錄到控制台:

爪哇
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) {}
});
節點.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) {}
});
節點.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);
節點.js
ref.off('value', originalCallback);

如果您將範圍上下文傳遞給on() ,則必須在分離回調時傳遞它:

爪哇
// Not applicable for Java
節點.js
ref.off('value', originalCallback, ctx);

如果您想刪除某個位置的所有回調,可以執行以下操作:

爪哇
// No Java equivalent, listeners must be removed individually.
節點.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) {
    // ...
  }
});
節點.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.");
  }

  // ...
});
節點.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) {
    // ...
  }

  // ...
});
節點.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());
  }

  // ...
});
節點.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());
  }

  // ...
});
節點.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)
}

有關使用orderByValue()時如何對null 、布爾值、字符串和對象值進行排序的說明,請參閱數據如何排序部分。

複雜查詢

現在您的數據是如何排序的已經很清楚了,您可以使用下面描述的限製範圍方法來構建更複雜的查詢。

限制查詢

limitToFirst()limitToLast()查詢用於設置要為給定回調同步的最大子節點數。如果您將限制設置為 100,您最初最多只能收到 100 個child_added事件。如果您的數據庫中存儲的消息少於 100 條,則每條消息都會觸發child_added事件。但是,如果您有超過 100 條消息,您將只會收到其中 100 條消息的child_added事件。如果您使用的是 limitToFirst() ,則這些是前 100 條有序消息;如果您使用的是limitToFirst() limitToLast()則這些是最後 100 條有序消息。隨著項目的變化,您將收到進入查詢的項目的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());
  }

  // ...
});
節點.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());
  }

  // ...
});
節點.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 名恐龍運動恐龍排行榜,您可以執行以下操作:

爪哇
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());
  }

  // ...
});
節點.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());
  }

  // ...
});
節點.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());
  }

  // ...
});
節點.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()來限制查詢的兩端。以下示例查找名稱以字母“b”開頭的所有恐龍:

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

  // ...
});
節點.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());
  }

  // ...
});
節點.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())
}

當您需要對數據進行分頁時,範圍查詢也很有用。

把它們放在一起

您可以結合所有這些技術來創建複雜的查詢。例如,您可以找到比劍龍短的恐龍名稱:

爪哇
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) {
    // ...
  }
});
節點.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()時,包含指定子鍵的數據按如下順序排列:

  1. 具有指定子鍵的null值的子項首先出現。
  2. 接下來是指定子鍵的值為false的子項。如果多個孩子的值為false ,則它們按字典順序鍵排序。
  3. 接下來是指定子鍵的值為true的子項。如果多個孩子的值為true ,則它們按字典順序鍵排序。
  4. 接下來是具有數值的子項,按升序排列。如果指定子節點的多個子節點的數值相同,則按key排序。
  5. 字符串在數字之後,並按字典順序升序排序。如果指定子節點的多個子節點具有相同的值,則它們按字典順序鍵排序。
  6. 對象排在最後,並按關鍵字按字典順序升序排序。

orderByKey

使用orderByKey()對數據進行排序時,數據按 key 升序返回,如下所示。請記住,鍵只能是字符串。

  1. 具有可以被解析為 32 位整數的鍵的子項排在最前面,按升序排序。
  2. 以字符串值作為鍵的子項緊隨其後,按字典順序升序排序。

按價值排序

使用orderByValue()時,子項按其值排序。排序標準與orderByChild()中的相同,除了使用節點的值而不是指定子鍵的值。