正在擷取資料

本文將說明擷取資料庫資料、資料排序方式,以及如何對資料執行簡單查詢的基本概念。在 Admin 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 會擷取值,將貼文轉換為物件,因此您可以分別呼叫 author 和 title,存取貼文的作者和標題屬性。您也可以透過第二個 prevChildKey 引數存取先前的貼文 ID。

子項已變更

每當子節點遭到修改,系統就會觸發 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(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、布林值、字串和物件值的排序方式。

複雜查詢

現在您已清楚瞭解資料的排序方式,可以使用下文所述的 limit 或 range 方法,建構更複雜的查詢。

限制查詢

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() 找出所有名稱在字典中排在 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('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() 相同,但會使用節點的值,而非指定子項鍵的值。