Truy xuất dữ liệu

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.

Tài liệu này trình bày những điều cơ bản về truy xuất dữ liệu cơ sở dữ liệu, cách dữ liệu được sắp xếp và cách thực hiện các truy vấn đơn giản trên dữ liệu. Truy xuất dữ liệu trong SDK quản trị được triển khai hơi khác nhau 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 được lưu trữ trong Cơ sở dữ liệu thời gian thực Firebase được truy xuất bằng cách đính kèm trình nghe không đồng bộ vào tham chiếu cơ sở dữ liệu. Trình nghe được kích hoạt một lần cho trạng thái ban đầu của dữ liệu và một lần nữa bất cứ khi nào dữ liệu thay đổi. Một trình lắng nghe sự kiện có thể nhận được một số loại sự kiện khác nhau. Chế độ truy xuất dữ liệu này được hỗ trợ trong SDK quản trị Java, Node.js và Python.
  2. Chặn đọc: Dữ liệu được lưu trữ trong Cơ sở dữ liệu thời gian thực Firebase được truy xuất bằng cách gọi phương thức chặn trên tham chiếu cơ sở dữ liệu, phương thức này trả về dữ liệu được lưu trữ tại tham chiếu. Mỗi cuộc gọi phương thức là một hoạt động một lần. Điều đó có nghĩa là SDK không đăng ký bất kỳ cuộc gọi lại nào lắng nghe các bản 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ị Python và Go.

Bắt đầu

Hãy xem lại ví dụ viết blog từ 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/save-data/fireblog/posts.json . Để đọc dữ liệu bài đăng của bạn, 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);
}); 
con trăn
# 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())
Đi

// 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 bạn chạy đoạn 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 đã đăng nhập vào bảng điều khiển. Trong trường hợp của Node.js và Java, hàm nghe đượ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 của bạn và bạn không cần phải viết thêm bất kỳ mã nào để thực hiện điều này.

Trong Java và Node.js, chức năng gọi lại nhận được DataSnapshot , đây là ảnh chụp nhanh dữ liệu. Ảnh chụp nhanh là hình ảnh của 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. Gọi val() / getValue() trên ảnh chụp nhanh sẽ trả về biểu diễn đối tượng dành riêng cho ngôn ngữ của dữ liệu. Nếu không có dữ liệu nào tồn tại tại vị trí của tham chiếu, thì giá trị của ảnh chụp nhanh là null . Phương get() trong Python trả về trực tiếp một biểu diễn Python của dữ liệu. Hàm Get() trong Go sắp xếp lại dữ liệu thành một cấu trúc dữ liệu nhất định.

Lưu ý rằng chúng tôi đã sử dụng loại sự kiện value trong ví dụ trên, loại này đọc toàn bộ nội dung của tham chiếu cơ sở dữ liệu Firebase, ngay cả khi chỉ một phần dữ liệu thay đổi. value là một trong năm loại sự kiện khác nhau đượ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 được sử dụng để đọc ảnh chụp nhanh tĩnh của 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. Nó được kích hoạt một lần với dữ liệu ban đầu và lặp lại mỗi khi dữ liệu thay đổi. Cuộc gọi lại sự kiện được chuyển qua một ảnh chụp nhanh chứa tất cả dữ liệu tại vị trí đó, bao gồm cả dữ liệu con. Trong ví dụ mã ở trên, value trả về tất cả các bài đăng trên blog trong ứng dụng của bạn. Mỗi khi một bài đăng blog mới được thêm vào, chức năng gọi lại sẽ trả về tất cả các bài đăng.

Đã thêm con

Sự kiện child_added thường được sử dụng khi truy xuất danh sách các mục từ 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 phần tử con hiện có và sau đó kích hoạt lại mỗi khi một phần tử con mới được thêm vào đường dẫn đã chỉ định. Cuộc gọi lại sự kiện được chuyển qua ảnh chụp nhanh có chứa dữ liệu của phần tử con mới. Đối với mục đích đặt hàng, nó cũng được truyền đối số thứ hai chứa khóa của phần tử con trước đó.

Nếu bạn chỉ muốn truy xuất dữ liệu trên mỗi bài đăng mới được thêm vào ứng dụng viết blog của mình, 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 có một bài đăng blog riêng lẻ. 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 ID bài đăng trước đó từ đối số prevChildKey thứ hai.

con đã thay đổi

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

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

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 con

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

Trong ví dụ về blog, bạn có thể sử dụng child_removed để ghi thông báo về bài đăng đã xóa 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');
});

con đã chuyển

Sự kiện child_moved được sử dụng khi làm việc với dữ liệu có thứ tự, sẽ được trình bày 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 về các sự kiện:

Đảm bảo sự kiện 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.
Các sự kiện cuối cùng 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 các hoạt động hoặc thời gian cục bộ 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.
Các ghi từ một máy khách sẽ luôn được ghi vào máy chủ và phát ra cho những người dùng khác theo thứ tự.
Các sự kiện 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ừ bất kỳ sự kiện nào khác đã xảy ra trước khi thực hiện ảnh chụp nhanh đó.

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

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

Tách cuộc gọi lại

Các cuộc gọi lại bị xóa bằng cách chỉ định loại sự kiện và chức năng gọi lại sẽ bị xóa, 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 bạn đã chuyển ngữ cảnh phạm vi vào on() , thì nó phải được chuyển khi tách hàm gọi lại:

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

Nếu bạn muốn xóa tất cả các cuộc 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ể hữu ích khi gọi lại một lần và sau đó bị xóa ngay lập tức. Chúng tôi đã tạo một chức năng trợ giúp để thực hiện điều này dễ dàng:

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
});
con trăn
# 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())
Đi
// 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 một cách có chọn lọc dựa trên nhiều yếu tố khác nhau. Để xây dựng một truy vấn trong cơ sở dữ liệu của bạn, bạn bắt đầu bằng cách chỉ định cách bạn muốn dữ liệu của mình được sắp xếp bằng cách sử dụ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 những phương thức này với năm 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 cho rằng khủng long rất thú vị, nên chúng tôi sẽ sử dụng một đoạn trích từ cơ sở dữ liệu mẫu về dữ kiện 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 ba cách: theo khóa con , theo khóa 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 này, mỗi hàm được giải thích bên dưới.

Sắp xếp theo khóa con được chỉ định

Bạn có thể sắp xếp các nút theo khóa con chung bằng cách chuyển khóa đó tới 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');
});
con trăn
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))
Đi

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

Bất kỳ nút nào không có khóa con mà chúng tôi đang truy vấn đều được sắp xếp với giá trị null , nghĩa là nó sẽ xuất hiện đầu tiên trong thứ tự. Để biết 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 .

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

{
  "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 phím 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');
});
con trăn
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))
Đi
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 khóa tại một thời điểm. Gọi orderByChild() nhiều lần trên cùng một truy vấn sẽ gây ra lỗi.

Đặt hàng theo khóa

Bạn cũng có thể sắp xếp các nút theo khóa 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);
});
con trăn
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().get()
print(snapshot)
Đi
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)

Thứ tự theo giá trị

Bạn có thể sắp xếp thứ tự các nút theo giá trị của các khóa con của chúng bằng phương thức orderByValue() . Giả sử những con khủng long đang 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 của chúng, bạn có thể xây dựng 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());
  });
});
con trăn
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))
Đi
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)
}

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

Truy vấn phức tạp

Giờ đây, khi đã rõ cách dữ liệu của bạn được sắp xếp, bạn có thể sử dụng các phương pháp giới hạn hoặc phạm vi được mô tả bên dưới để xây dựng 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() được sử dụng để đặt số lượng phần tử con tối đa được đồng bộ hóa cho một cuộc gọi lại nhất định. Nếu bạn đặt giới hạn là 100, ban đầu bạn sẽ chỉ nhận được tối đa 100 child_added kiện được thêm vào. Nếu bạn có ít hơn 100 thư được lưu trữ trong cơ sở dữ liệu của mình, thì sự kiện child_added sẽ kích hoạt cho mỗi thư. Tuy nhiên, nếu bạn có hơn 100 tin nhắn, bạn sẽ chỉ nhận được sự kiện child_added cho 100 tin nhắn trong số đó. Đây là 100 tin nhắn được sắp xếp đầu tiên nếu bạn đang sử dụng limitToFirst() hoặc 100 tin nhắn được sắp xếp 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 các sự kiện child_added cho các mục tham gia truy vấn và các sự kiện child_removed cho các mục rời khỏi truy vấn, để tổng số vẫn là 100.

Sử dụng cơ sở dữ liệu về khủng long và orderByChild() , bạn có thể tìm thấy hai loài 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);
});
con trăn
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('weight').limit_to_last(2).get()
for key in snapshot:
    print(key)
Đi
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())
}

Hàm gọi lại child_added được kích hoạt chính xác hai lần, trừ khi có ít hơn hai con khủng long được lưu trữ trong cơ sở dữ liệu. Nó cũng sẽ bị sa thải đố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 trả về trực tiếp 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);
});
con trăn
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').limit_to_first(2).get()
for key in snapshot:
    print(key)
Đi
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())
}

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

Bạn cũng có thể thực hiện các truy vấn giới hạn với orderByValue() . Nếu bạn muốn tạo bảng xếp hạng với 3 con khủng long thể thao khủng long 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());
  });
});
con trăn
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))
Đi
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)
}

Phạm vi truy vấn

Việc sử dụng startAt() , endAt()equalTo() cho phép bạn chọn điểm bắt đầu và điểm kết thúc tùy ý cho các truy vấn của mình. Ví dụ: nếu bạn muốn tìm tất cả khủng long cao ít nhất ba 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);
});
con trăn
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').start_at(3).get()
for key in snapshot:
    print(key)
Đi
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 trước Pterodactyl theo từ điển:

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);
});
con trăn
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().end_at('pterodactyl').get()
for key in snapshot:
    print(key)
Đi
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 truy vấn của mình. Ví dụ sau tìm tất cả các loài khủng long có tên bắt đầu bằng chữ "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);
});
con trăn
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().start_at('b').end_at(u'b\uf8ff').get()
for key in snapshot:
    print(key)
Đi
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 equalTo() cho phép bạn lọc dựa trên kết quả khớp chính xác. Như trường hợp của các truy vấn phạm vi khác, nó sẽ kích hoạt cho từng nút con phù hợp. Ví dụ: bạn có thể sử dụng truy vấn 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);
});
con trăn
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').equal_to(25).get()
for key in snapshot:
    print(key)
Đi
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.

Để tất cả chúng cùng nhau

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 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');
      }
    });
});
con trăn
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')
Đi
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ủa bạn được sắp xếp khi sử dụng một trong bốn chức năng sắp xếp.

orderByChild

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

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

orderByKey

Khi sử dụng orderByKey() để sắp xếp dữ liệu của bạn, dữ liệu được trả về theo thứ tự tăng dần theo khóa như sau. Hãy nhớ rằng khóa chỉ có thể là chuỗi.

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

orderByValue

Khi sử dụng orderByValue() , trẻ em được sắp xếp theo giá trị của chúng. Tiêu chí đặt hàng giống như trong orderByChild() , ngoại trừ giá trị của nút được sử dụng thay vì giá trị của khóa con đã chỉ định.