正在擷取資料

本文將介紹如何擷取資料庫資料、資料排序方式,以及如何對資料執行簡單查詢。在不同的程式設計語言中,管理 SDK 的資料擷取實作方式略有不同。

  1. 非同步事件監聽器: 如要擷取儲存在 Firebase Realtime Database 中的資料,請將非同步事件監聽器附加至資料庫參照。監聽器會針對資料的初始狀態觸發一次,並且在資料變更時會再次觸發。事件監聽器可能會收到幾種不同的事件類型。Java、Node.js 和 Python Admin SDK 均支援這種資料擷取模式。
  2. 封鎖讀取: 如要擷取儲存在 Firebase Realtime Database 中的資料,請在資料庫參照上叫用封鎖方法,系統會傳回儲存在該參照中的資料。每次方法呼叫都是一次性作業。也就是說,SDK 不會註冊任何監聽後續資料更新的回呼。 Python 和 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(f'{key} was {val} meters tall')
Go
// Dinosaur is a json-serializable type.
type Dinosaur struct {
	Height int `json:"height"`
	Width  int `json:"width"`
}

ref := client.NewRef("dinosaurs")

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

如果節點沒有我們查詢的子項鍵,系統會以 null 值排序,也就是說,這類節點會排在最前面。如要瞭解資料排序方式,請參閱「資料排序方式」一節。

查詢也可以依深層巢狀子項排序,而不僅限於下一層的子項。如果您有類似下列的深層巢狀資料,這項功能就非常實用:

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

如要立即查詢高度,可以使用物件的完整路徑,而非單一鍵:

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

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

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

查詢一次只能依一個鍵排序。對同一查詢多次呼叫 orderByChild() 會擲回錯誤。

依鍵排序

您也可以使用 orderByKey() 方法依節點鍵排序節點。以下範例會依字母順序讀取所有恐龍:

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

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

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

依值排序

您可以使用 orderByValue() 方法,依節點子項鍵的值排序節點。假設恐龍正在舉辦恐龍運動競賽,而您要以以下格式追蹤牠們的分數:

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

如要依分數排序恐龍,可以建構下列查詢:

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

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

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

如要瞭解使用 orderByValue() 時,系統如何排序 null、布林值、字串和物件值,請參閱「資料排序方式」一節。

複雜查詢

現在您已瞭解資料的排序方式,可以運用下文所述的 limitrange 方法,建構更複雜的查詢。

限制查詢

limitToFirst()limitToLast() 查詢用於設定特定回呼要同步處理的子項數量上限。如果將上限設為 100,您一開始只會收到最多 100 個 child_added 事件。如果資料庫中儲存的訊息少於 100 則,系統會為每則訊息觸發 child_added 事件。不過,如果訊息超過 100 則,系統只會針對其中 100 則訊息傳送 child_added 事件。如果您使用 limitToFirst(),這些是前 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() 執行限制查詢。如要建立排行榜,列出得分最高的前 3 名恐龍運動員,可以執行下列操作:

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

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

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

範圍查詢

使用 startAt()endAt()equalTo(),即可為查詢選擇任意起點和終點。舉例來說,如要找出所有高度至少三公尺的恐龍,可以合併 orderByChild()startAt()

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

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

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

您可以使用 endAt() 找出名稱在字典順序中位於「翼龍」之前的所有恐龍:

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('b\uf8ff').get()
for key in snapshot:
    print(key)
Go
ref := client.NewRef("dinosaurs")

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

equalTo() 方法可讓您根據完全相符的結果進行篩選。與其他範圍查詢一樣,系統會針對每個相符的子節點觸發此查詢。舉例來說,您可以使用下列查詢,找出所有 25 公尺高的恐龍:

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

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

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

需要將資料分頁時,範圍查詢也很有用。

正在統整內容

您可以結合所有這些技巧來建立複雜的查詢。舉例來說,您可以找出比劍龍短的恐龍名稱:

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

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

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

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

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

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

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

資料排序方式

本節說明使用四種排序函式時,資料的排序方式。

orderByChild

使用 orderByChild() 時,含有指定子項鍵的資料會依下列方式排序:

  1. 指定子項鍵的 null 值會優先顯示。
  2. 接著是指定子項鍵的值為 false 的子項。如果多個子項的值為 false,系統會依鍵字典順序排序。
  3. 接著是指定子項鍵的值為 true 的子項。如果多個子項的值為 true,系統會依鍵的字典順序排序。
  4. 接著是數值,並以遞增順序排序。如果多個子項的指定子節點具有相同的數值,系統會依鍵排序。
  5. 字串會排在數字後面,並依字母順序遞增排序。如果多個子項的指定子節點值相同,系統會依鍵的字典順序排序。
  6. 物件會排在最後,並依鍵的字母順序遞增排序。

orderByKey

使用 orderByKey() 排序資料時,系統會依鍵值升序傳回資料,如下所示。請注意,鍵只能是字串。

  1. 如果子項含有可剖析為 32 位元整數的鍵,系統會先顯示這類子項,並依遞增順序排序。
  2. 接著是金鑰為字串值的子項,並依字典順序遞增排序。

orderByValue

使用 orderByValue() 時,子項會依值排序。排序條件與 orderByChild() 相同,只是會使用節點的值,而不是指定子項鍵的值。