正在擷取資料

本文件將說明擷取資料庫資料的基本概念、資料的排序方式,以及如何對資料執行簡單查詢。在不同程式設計語言中,Admin SDK 的資料擷取方式略有不同。

  1. 非同步事件監聽器:將非同步事件監聽器附加至資料庫參照,即可擷取儲存在 Firebase Realtime Database 中的資料。監聽器會針對資料的初始狀態觸發一次,並且在資料變更時會再次觸發。事件監聽器可能會接收多種事件類型。Java、Node.js 和 Python 管理員 SDK 支援這種資料擷取模式。
  2. 封鎖讀取:在資料庫參照上叫用封鎖方法,藉此擷取儲存在 Firebase Realtime Database 中的資料,該方法會傳回儲存在參照中的資料。每個方法呼叫都是一次性作業。也就是說,SDK 不會註冊任何會監聽後續資料更新的回呼。Python 和 Go 管理員 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、布林值、字串和物件值,請參閱「資料排序方式」一節。

複雜查詢

資料的排序方式已清楚顯示,您可以使用下方所述的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('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() 相同,只是使用節點的值,而非指定子項鍵的值。