Truy xuất dữ liệu

Tài liệu này trình bày những thông tin cơ bản về việc truy xuất dữ liệu trong cơ sở dữ liệu, cách sắp xếp dữ liệu và cách thực hiện các truy vấn đơn giản về dữ liệu. Tính năng truy xuất dữ liệu trong SDK dành cho quản trị viên được triển khai hơi khác trên các ngôn ngữ lập trình khác nhau.

  1. Trình nghe không đồng bộ: Dữ liệu lưu trữ trong Cơ sở dữ liệu theo thời gian thực Firebase được truy xuất bằng cách đính kèm một trình nghe không đồng bộ vào một giá trị tham chiếu cơ sở dữ liệu. Trình nghe này được kích hoạt một lần đối với trạng thái ban đầu của dữ liệu và kích hoạt lại bất cứ khi nào dữ liệu thay đổi. Trình nghe sự kiện có thể nhận một số loại sự kiện. Chế độ truy xuất dữ liệu này được hỗ trợ trong SDK quản trị của Java, Node.js và Python.
  2. Quy tắc chặn đọc: Dữ liệu lưu trữ trong Cơ sở dữ liệu theo thời gian thực của Firebase được truy xuất bằng cách gọi một phương thức chặn trên tham chiếu cơ sở dữ liệu. Phương thức này sẽ trả về dữ liệu được lưu trữ tại tham chiếu. Mỗi lệnh gọi phương thức là một thao tác một lần. Điều đó có nghĩa là SDK không đăng ký bất kỳ lệnh gọi lại nào để theo dõi các lượt cập nhật dữ liệu tiếp theo. Mô hình truy xuất dữ liệu này được hỗ trợ trong SDK Quản trị viên của Python và Go.

Bắt đầu

Hãy cùng xem lại ví dụ về cách viết blog trong bài viết trước để hiểu cách đọc dữ liệu từ cơ sở dữ liệu Firebase. Hãy nhớ rằng các bài đăng trên blog trong ứng dụng mẫu được lưu trữ tại URL cơ sở dữ liệu https://docs-examples.firebaseio.com/server/saving-data/fireblog/post.json. Để đọc dữ liệu bài đăng, bạn có thể làm như sau:

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())
Tiến hành

// 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)
}

Nếu chạy mã trên, bạn sẽ thấy một đối tượng chứa tất cả các bài đăng của bạn được ghi vào bảng điều khiển. Đối với Node.js và Java, hàm trình nghe sẽ được gọi bất cứ khi nào dữ liệu mới được thêm vào tham chiếu cơ sở dữ liệu và bạn không cần viết thêm bất kỳ mã nào để thực hiện việc này.

Trong Java và Node.js, hàm callback nhận được DataSnapshot. Đây là thông tin tổng quan nhanh về dữ liệu. Tổng quan nhanh là hình ảnh về dữ liệu tại một tham chiếu cơ sở dữ liệu cụ thể tại một thời điểm duy nhất. Việc gọi val() / getValue() trên ảnh chụp nhanh sẽ trả về bản trình bày dữ liệu theo đối tượng cụ thể theo ngôn ngữ. Nếu không tồn tại dữ liệu tại vị trí của tệp đối chiếu, thì giá trị của ảnh chụp nhanh sẽ là null. Phương thức get() trong Python trực tiếp trả về bản trình bày dữ liệu Python. Hàm Get() trong Go chưa sắp xếp dữ liệu thành một cấu trúc dữ liệu nhất định.

Xin lưu ý rằng chúng ta đã sử dụng loại sự kiện value trong ví dụ ở trên để đọc toàn bộ nội dung của tham chiếu cơ sở dữ liệu Firebase, ngay cả khi chỉ có một phần dữ liệu thay đổi. value là một trong 5 loại sự kiện được liệt kê bên dưới mà bạn có thể sử dụng để đọc dữ liệu từ cơ sở dữ liệu.

Đọc các loại sự kiện trong Java và Node.js

Giá trị

Sự kiện value dùng để đọc thông tin tổng quan nhanh tĩnh về nội dung tại một đường dẫn cơ sở dữ liệu nhất định, vì chúng tồn tại tại thời điểm diễn ra sự kiện đọc. Chuỗi này được kích hoạt một lần với dữ liệu ban đầu và kích hoạt lại mỗi khi dữ liệu thay đổi. Lệnh gọi lại sự kiện được truyền một thông tin tổng quan nhanh chứa tất cả dữ liệu tại vị trí đó, bao gồm cả dữ liệu con. Trong ví dụ về mã ở trên, value trả về tất cả bài đăng trên blog trong ứng dụng của bạn. Mỗi lần một bài đăng mới được thêm trên blog, hàm callback sẽ trả về tất cả bài đăng.

Đã thêm phần tử con

Sự kiện child_added thường được dùng khi truy xuất danh sách các mặt hàng qua cơ sở dữ liệu. Không giống như value trả về toàn bộ nội dung của vị trí, child_added được kích hoạt một lần cho mỗi thành phần con hiện có và sau đó kích hoạt lại mỗi khi một thành phần con mới được thêm vào đường dẫn đã chỉ định. Lệnh gọi lại sự kiện được truyền một ảnh chụp nhanh có chứa dữ liệu của thành phần con mới. Đối với mục đích sắp xếp, hàm này cũng được truyền một đối số thứ hai có chứa khoá của thành phần con trước đó.

Nếu chỉ muốn truy xuất dữ liệu về từng bài đăng mới được thêm vào ứng dụng viết blog, bạn có thể sử dụng 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);
});

Trong ví dụ này, ảnh chụp nhanh sẽ chứa một đối tượng kèm theo một bài đăng riêng lẻ trên blog. Vì SDK chuyển đổi bài đăng thành đối tượng bằng cách truy xuất giá trị nên bạn có quyền truy cập vào thuộc tính tác giả và tiêu đề của bài đăng bằng cách gọi authortitle tương ứng. Bạn cũng có quyền truy cập vào mã bài đăng trước đó từ đối số prevChildKey thứ hai.

Đã thay đổi phần tử con

Sự kiện child_changed được kích hoạt bất cứ khi nào một nút con được sửa đổi. Điều này bao gồm mọi sửa đổi đối với các thành phần con của nút con. Thuộc tính này thường được dùng kết hợp với child_addedchild_removed để phản hồi các thay đổi đối với danh sách các mục. Bản tổng quan nhanh được chuyển đến lệnh gọi lại sự kiện chứa dữ liệu cập nhật cho khung hiển thị con.

Bạn có thể dùng child_changed để đọc dữ liệu cập nhật trên các bài đăng trên blog khi chỉnh sửa các bài đăng đó:

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);
});

Đã xóa trẻ em

Sự kiện child_removed được kích hoạt khi một thành phần con ngay lập tức bị xoá. Hàm này thường được dùng cùng với child_addedchild_changed. Ảnh chụp nhanh được chuyển đến lệnh gọi lại sự kiện chứa dữ liệu của thành phần con đã bị xoá.

Trong ví dụ về blog, bạn có thể sử dụng child_removed để ghi lại thông báo về bài đăng đã xoá vào bảng điều khiển:

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');
});

Trẻ đã di chuyển

Sự kiện child_moved được dùng khi làm việc với dữ liệu có thứ tự như được đề cập trong phần tiếp theo.

Đảm bảo sự kiện

Cơ sở dữ liệu Firebase đưa ra một số đảm bảo quan trọng đối với các sự kiện:

Bảo hiểm cho sự kiện của cơ sở dữ liệu
Các sự kiện sẽ luôn được kích hoạt khi trạng thái cục bộ thay đổi.
Cuối cùng, các sự kiện sẽ luôn phản ánh trạng thái chính xác của dữ liệu, ngay cả trong trường hợp hoạt động cục bộ hoặc thời gian gây ra sự khác biệt tạm thời, chẳng hạn như mất kết nối mạng tạm thời.
Nội dung ghi từ một ứng dụng sẽ luôn được ghi lên máy chủ và được truyền đến người dùng khác theo thứ tự.
Các sự kiện có giá trị luôn được kích hoạt sau cùng và được đảm bảo chứa thông tin cập nhật từ mọi sự kiện khác đã xảy ra trước khi dữ liệu tổng quan nhanh đó được chụp.

Vì các sự kiện có giá trị luôn được kích hoạt sau cùng, nên ví dụ sau sẽ luôn phù hợp:

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);
});

Huỷ lệnh gọi lại

Các lệnh gọi lại bị xoá bằng cách chỉ định loại sự kiện và hàm callback cần xoá, chẳng hạn như sau:

Java
// Create and attach listener
ValueEventListener listener = new ValueEventListener() {
    // ...
};
ref.addValueEventListener(listener);

// Remove listener
ref.removeEventListener(listener);
Node.js
ref.off('value', originalCallback);

Nếu đã truyền ngữ cảnh phạm vi vào on(), thì bạn phải truyền ngữ cảnh phạm vi đó khi tách lệnh gọi lại:

Java
// Not applicable for Java
Node.js
ref.off('value', originalCallback, ctx);

Nếu muốn xoá tất cả lệnh gọi lại tại một vị trí, bạn có thể làm như sau:

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();

Đọc dữ liệu một lần

Trong một số trường hợp, có thể bạn nên gọi lệnh gọi lại một lần rồi sau đó bị xoá ngay lập tức. Chúng tôi đã tạo một hàm trợ giúp để giúp bạn dễ dàng thực hiện việc này:

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())
Tiến hành
// 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)
}

Truy vấn dữ liệu

Với các truy vấn cơ sở dữ liệu Firebase, bạn có thể truy xuất dữ liệu có chọn lọc dựa trên nhiều yếu tố. Để tạo truy vấn trong cơ sở dữ liệu, bạn hãy bắt đầu bằng cách chỉ định cách sắp xếp dữ liệu bằng một trong các hàm sắp xếp: orderByChild(), orderByKey() hoặc orderByValue(). Sau đó, bạn có thể kết hợp các phương thức này với 5 phương thức khác để thực hiện các truy vấn phức tạp: limitToFirst(), limitToLast(), startAt(), endAt()equalTo().

Vì tất cả chúng tôi tại Firebase đều nghĩ rằng khủng long khá thú vị, nên chúng tôi sẽ sử dụng một đoạn mã từ cơ sở dữ liệu mẫu về khủng long để minh họa cách bạn có thể truy vấn dữ liệu trong cơ sở dữ liệu Firebase của mình.:

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

Bạn có thể sắp xếp dữ liệu theo 3 cách: theo khoá con, theo khoá hoặc theo giá trị. Truy vấn cơ sở dữ liệu cơ bản bắt đầu bằng một trong các hàm sắp xếp sau, mỗi hàm được giải thích ở bên dưới.

Sắp xếp theo một khoá con đã chỉ định

Bạn có thể sắp xếp các nút theo một khoá con chung bằng cách truyền khoá đó vào orderByChild(). Ví dụ: để đọc tất cả các loại khủng long được sắp xếp theo chiều cao, bạn có thể làm như sau:

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))
Tiến hành

// 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)
}

Mọi nút không có khoá con mà chúng ta đang truy vấn đều được sắp xếp với giá trị null, có nghĩa là nút đó sẽ xuất hiện đầu tiên theo thứ tự. Để biết thông tin chi tiết về cách sắp xếp dữ liệu, hãy xem phần Cách sắp xếp dữ liệu.

Bạn cũng có thể sắp xếp các truy vấn theo các thành phần con được lồng sâu, thay vì chỉ các thành phần con một cấp. Điều này rất hữu ích nếu bạn có dữ liệu lồng ghép sâu như sau:

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

Để truy vấn chiều cao ngay bây giờ, bạn có thể sử dụng đường dẫn đầy đủ đến đối tượng thay vì một khoá duy nhất:

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))
Tiến hành
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)
}

Các truy vấn chỉ có thể sắp xếp theo một khoá tại một thời điểm. Việc gọi orderByChild() nhiều lần cho cùng một truy vấn sẽ gây ra lỗi.

Sắp xếp theo phím

Bạn cũng có thể sắp xếp các nút theo khoá của chúng bằng phương thức orderByKey(). Ví dụ sau đây đọc tất cả các loại khủng long theo thứ tự bảng chữ cái:

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)
Tiến hành
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)

Sắp xếp theo giá trị

Bạn có thể sắp xếp các nút theo giá trị của các khoá con bằng phương thức orderByValue(). Giả sử khủng long đang tổ chức một cuộc thi thể thao khủng long và bạn đang theo dõi điểm số của chúng theo định dạng sau:

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

Để sắp xếp khủng long theo điểm số, bạn có thể tạo truy vấn sau:

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))
Tiến hành
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)
}

Vui lòng xem phần Cách sắp xếp dữ liệu để biết nội dung giải thích về cách sắp xếp null, boolean, chuỗi và giá trị đối tượng khi sử dụng orderByValue().

Truy vấn phức tạp

Giờ đây, khi đã nắm rõ cách sắp xếp dữ liệu, bạn có thể sử dụng phương thức giới hạn hoặc dải_ô được mô tả dưới đây để tạo các truy vấn phức tạp hơn.

Giới hạn truy vấn

Các truy vấn limitToFirst()limitToLast() dùng để đặt số lượng phần tử con tối đa cần đồng bộ hoá cho một lệnh gọi lại nhất định. Nếu đặt giới hạn là 100 sự kiện, thì ban đầu, bạn sẽ chỉ nhận được tối đa 100 sự kiện child_added. Nếu bạn lưu trữ ít hơn 100 thông báo trong cơ sở dữ liệu, thì sự kiện child_added sẽ kích hoạt cho từng thông báo. Tuy nhiên, nếu có hơn 100 thông báo, bạn sẽ chỉ nhận được sự kiện child_added cho 100 thông báo trong số đó. Đây là 100 thông báo được sắp xếp theo thứ tự đầu tiên nếu bạn đang sử dụng limitToFirst() hoặc 100 thông báo được sắp xếp theo thứ tự cuối cùng nếu bạn đang sử dụng limitToLast(). Khi các mục thay đổi, bạn sẽ nhận được sự kiện child_added cho các mục nhập truy vấn và sự kiện child_removed cho các mục rời khỏi truy vấn đó, để tổng số vẫn ở mức 100.

Dựa trên cơ sở dữ liệu về khủng long và orderByChild(), bạn có thể tìm thấy hai con khủng long nặng nhất:

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)
Tiến hành
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())
}

Lệnh gọi lại child_added được kích hoạt đúng 2 lần, trừ phi có ít hơn 2 con khủng long được lưu trữ trong cơ sở dữ liệu. Hệ thống cũng sẽ kích hoạt tính năng này đối với mỗi con khủng long mới, nặng hơn được thêm vào cơ sở dữ liệu. Trong Python, truy vấn này trực tiếp trả về một OrderedDict chứa hai con khủng long nặng nhất.

Tương tự, bạn có thể tìm thấy hai con khủng long ngắn nhất bằng cách sử dụng 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)
Tiến hành
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())
}

Lệnh gọi lại child_added được kích hoạt đúng 2 lần, trừ phi có ít hơn 2 con khủng long được lưu trữ trong cơ sở dữ liệu. Mặt hàng này cũng sẽ bị kích hoạt lại nếu xoá 1 trong 2 con khủng long đầu tiên khỏi cơ sở dữ liệu, vì bây giờ con khủng long mới sẽ là con khủng long ngắn nhất thứ hai. Trong Python, truy vấn này trực tiếp trả về một OrderedDict chứa những con khủng long ngắn nhất.

Bạn cũng có thể tiến hành giới hạn truy vấn bằng orderByValue(). Nếu muốn tạo bảng xếp hạng có 3 khủng long khủng long thể thao có điểm số cao nhất, bạn có thể làm như sau:

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))
Tiến hành
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)
}

Truy vấn dải ô

Bằng cách sử dụng startAt(), endAt()equalTo(), bạn có thể chọn điểm bắt đầu và điểm kết thúc tuỳ ý cho các truy vấn của mình. Ví dụ: nếu muốn tìm tất cả các con khủng long cao ít nhất 3 mét, bạn có thể kết hợp 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)
Tiến hành
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())
}

Bạn có thể sử dụng endAt() để tìm tất cả các loài khủng long có tên xuất hiện trước Pterodactyl theo thuật ngữ:

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)
Tiến hành
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())
}

Bạn có thể kết hợp startAt()endAt() để giới hạn cả hai đầu của truy vấn. Ví dụ sau đây tìm tất cả các loài khủng long có tên bắt đầu bằng chữ cái "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)
Tiến hành
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())
}

Phương thức equalTo() cho phép bạn lọc dựa trên các kết quả khớp chính xác. Tương tự như với các truy vấn phạm vi khác, phương thức này sẽ kích hoạt cho mỗi nút con phù hợp. Ví dụ: bạn có thể sử dụng cụm từ tìm kiếm sau để tìm tất cả các loài khủng long cao 25 mét:

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)
Tiến hành
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())
}

Truy vấn phạm vi cũng hữu ích khi bạn cần phân trang dữ liệu của mình.

Đang kết hợp

Bạn có thể kết hợp tất cả các kỹ thuật này để tạo các truy vấn phức tạp. Ví dụ: bạn có thể tìm thấy tên của loài khủng long ngắn hơn loài khủng long 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')
Tiến hành
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")
}

Cách dữ liệu được sắp xếp

Phần này giải thích cách dữ liệu được sắp xếp khi sử dụng từng hàm trong số 4 hàm sắp xếp.

orderByChild

Khi sử dụng orderByChild(), dữ liệu chứa khoá con đã chỉ định được sắp xếp như sau:

  1. Các phần tử con có giá trị null trong khoá con được chỉ định sẽ xuất hiện trước.
  2. Các phần tử con có giá trị false cho khoá con đã chỉ định sẽ xuất hiện tiếp theo. Nếu nhiều phần tử con có giá trị false, thì các phần tử con đó sẽ được sắp xếp theo từ vựng theo khoá.
  3. Các phần tử con có giá trị true cho khoá con đã chỉ định sẽ xuất hiện tiếp theo. Nếu nhiều phần tử con có giá trị true, thì các phần tử con này sẽ được sắp xếp theo từ vựng theo khoá.
  4. Các phần tử con có giá trị số sẽ xuất hiện tiếp theo, được sắp xếp theo thứ tự tăng dần. Nếu nhiều nút con cháu có cùng giá trị số đối với nút con được chỉ định, thì các nút con đó sẽ được sắp xếp theo khoá.
  5. Các chuỗi đứng sau số và được sắp xếp theo từ vựng theo thứ tự tăng dần. Nếu nhiều nút con cháu có cùng giá trị với nút con đã chỉ định, chúng được sắp xếp theo từ vựng theo khoá.
  6. Các đối tượng nằm ở cuối cùng và được sắp xếp từ vựng theo khoá theo thứ tự tăng dần.

orderByKey

Khi sử dụng orderByKey() để sắp xếp dữ liệu, dữ liệu sẽ được trả về theo thứ tự tăng dần theo khoá như sau. Lưu ý rằng khoá chỉ có thể là chuỗi.

  1. Phần tử con có khoá có thể phân tích cú pháp dưới dạng số nguyên 32 bit sẽ xuất hiện trước, được sắp xếp theo thứ tự tăng dần.
  2. Những trẻ chứa giá trị chuỗi là khoá tiếp theo, được sắp xếp theo từ vựng theo thứ tự tăng dần.

đơn hàng theo giá trị

Khi sử dụng orderByValue(), phần tử con được sắp xếp theo giá trị. Tiêu chí sắp xếp giống như trong orderByChild(), ngoại trừ việc giá trị của nút được dùng thay cho giá trị của khoá con đã chỉ định.