Thông tin khái quát
Các thao tác trong quy trình là một giao diện truy vấn mới cho Cloud Firestore. Giao diện này cung cấp chức năng truy vấn nâng cao, bao gồm cả các biểu thức phức tạp. Phiên bản Firestore Enterprise hỗ trợ các phép kết hợp theo kiểu quan hệ thông qua truy vấn con tương quan. Không giống như nhiều cơ sở dữ liệu NoSQL thường yêu cầu chuẩn hoá dữ liệu hoặc thực hiện nhiều yêu cầu phía máy khách, truy vấn con cho phép bạn kết hợp và tổng hợp dữ liệu từ các bộ sưu tập hoặc bộ sưu tập con có liên quan ngay trên máy chủ.
Truy vấn con là các biểu thức thực thi một quy trình lồng nhau cho mọi tài liệu được truy vấn bên ngoài xử lý. Điều này cho phép các mẫu truy xuất dữ liệu phức tạp, chẳng hạn như tìm nạp một tài liệu cùng với các mục trong bộ sưu tập con có liên quan hoặc kết hợp dữ liệu được liên kết một cách logic trên các bộ sưu tập gốc riêng biệt.
Khái niệm
Phần này giới thiệu các khái niệm cốt lõi đằng sau việc sử dụng truy vấn con để thực hiện các phép kết hợp trong các thao tác trong quy trình.
Truy vấn con dưới dạng biểu thức
Truy vấn con không phải là giai đoạn cấp cao nhất; thay vào đó, đây là một biểu thức có
thể được sử dụng trong mọi giai đoạn chấp nhận biểu thức, chẳng hạn như
select(...),
add_fields(...),
where(...), hoặc sort(...).
Cloud Firestore hỗ trợ 3 loại truy vấn con:
- Truy vấn con dạng mảng: Hiện thực hoá toàn bộ tập kết quả của truy vấn con dưới dạng một mảng tài liệu.
- Truy vấn con dạng vô hướng: Đánh giá thành một giá trị duy nhất, chẳng hạn như số lượng, giá trị trung bình hoặc một trường cụ thể từ một tài liệu có liên quan.
- Truy vấn con
subcollection(...): các phép kết hợp đơn giản hoá cho mối quan hệ một-nhiều giữa cha mẹ và con.
Phạm vi và biến
Khi viết một phép kết hợp, truy vấn con lồng nhau thường cần tham chiếu đến các trường từ tài liệu "bên ngoài" (tài liệu mẹ). Để kết nối các phạm vi này, bạn sử dụng giai đoạn
let(...) (được gọi là define(...) trong một số
SDK) để xác định các biến trong phạm vi mẹ mà sau đó có thể được tham chiếu trong
truy vấn con bằng hàm variable(...).
Cú pháp
Các phần sau đây cung cấp thông tin tổng quan về cú pháp để thực hiện các phép kết hợp.
Giai đoạn let(...)
Giai đoạn let(...) (được gọi là define(...) trong một số
SDK) là giai đoạn không lọc, giúp đưa dữ liệu một cách rõ ràng từ phạm vi mẹ
vào một biến có tên để sử dụng trong các phạm vi lồng nhau tiếp theo.
Web
async function defineStageData() {
await setDoc(doc(collection(db, "Authors"), "author_123"), {
"id": "author_123",
"name": "Jane Austen"
});
}
Swift
func defineStageData() async throws { try await db.collection("authors").document("author_123").setData([ "id": "author_123", "name": "Jane Austen" ]) }
Kotlin
fun defineStageData() { val author = hashMapOf( "id" to "author_123", "name" to "Jane Austen", ) db.collection("Authors").document("author_123").set(author) }
Java
public void defineStageData() { Map<String, Object> author = new HashMap<>(); author.put("id", "author_123"); author.put("name", "Jane Austen"); db.collection("Authors").document("author_123").set(author); }
Truy vấn con dạng mảng
Truy vấn con dạng mảng là một trường hợp đặc biệt của truy vấn con dạng biểu thức, giúp hiện thực hoá toàn bộ tập kết quả của truy vấn con thành một mảng. Nếu truy vấn con trả về 0 hàng, thì truy vấn con đó sẽ đánh giá thành một mảng trống. Truy vấn con này không bao giờ trả về một mảng null. Các truy vấn như vậy rất hữu ích khi bạn cần kết quả đầy đủ trong kết quả cuối cùng, chẳng hạn như khi hiện thực hoá một bộ sưu tập lồng nhau hoặc tương quan.
Các truy vấn có thể lọc, sắp xếp và tổng hợp trong truy vấn con để giảm lượng dữ liệu cần tìm nạp và trả về, giúp giảm chi phí truy vấn. Thứ tự của truy vấn con được tuân thủ, nghĩa là giai đoạn sort(...) trong truy vấn con sẽ kiểm soát thứ tự của kết quả trong mảng cuối cùng.
Sử dụng trình bao bọc SDK toArrayExpression() để chuyển đổi một truy vấn thành một mảng.
Web
async function toArrayExpressionStageData() {
await setDoc(doc(collection(db, "Projects"), "project_1"), {
"id": "project_1",
"name": "Alpha Build"
});
await addDoc(collection(db, "Tasks"), {
"project_id": "project_1",
"title": "System Architecture"
});
await addDoc(collection(db, "Tasks"), {
"project_id": "project_1",
"title": "Database Schema Design"
});
}
Đáp
{
id: "project_1",
name: "Alpha Build",
taskTitles: [
"System Architecture", "Database Schema Design"
]
}
Swift
async function toArrayExpressionStageData() { await setDoc(doc(collection(db, "Projects"), "project_1"), { "id": "project_1", "name": "Alpha Build" }); await addDoc(collection(db, "Tasks"), { "project_id": "project_1", "title": "System Architecture" }); await addDoc(collection(db, "Tasks"), { "project_id": "project_1", "title": "Database Schema Design" }); }
Đáp
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
Kotlin
fun toArrayExpressionData() { val project = hashMapOf( "id" to "project_1", "name" to "Alpha Build", ) db.collection("Projects").document("project_1").set(project) val task1 = hashMapOf( "project_id" to "project_1", "title" to "System Architecture", ) db.collection("Tasks").add(task1) val task2 = hashMapOf( "project_id" to "project_1", "title" to "Database Schema Design", ) db.collection("Tasks").add(task2) }
Đáp
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
Java
public void toArrayExpressionData() { Map<String, Object> project = new HashMap<>(); project.put("id", "project_1"); project.put("name", "Alpha Build"); db.collection("Projects").document("project_1").set(project); Map<String, Object> task1 = new HashMap<>(); task1.put("project_id", "project_1"); task1.put("title", "System Architecture"); db.collection("Tasks").add(task1); Map<String, Object> task2 = new HashMap<>(); task2.put("project_id", "project_1"); task2.put("title", "Database Schema Design"); db.collection("Tasks").add(task2); }
Đáp
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
Truy vấn con dạng vô hướng
Truy vấn con dạng vô hướng thường được sử dụng trong giai đoạn select(...) hoặc
where(...) vì cho phép lọc hoặc tạo kết quả của
truy vấn con mà không hiện thực hoá trực tiếp toàn bộ truy vấn.
Truy vấn con dạng vô hướng tạo ra 0 kết quả sẽ tự đánh giá thành null, trong khi truy vấn con đánh giá thành nhiều phần tử sẽ dẫn đến lỗi thời gian chạy.
Khi truy vấn con dạng vô hướng chỉ tạo ra một trường duy nhất cho mỗi kết quả, trường đó sẽ được nâng cao để trở thành kết quả cấp cao nhất cho truy vấn con. Trường hợp này thường thấy nhất
khi truy vấn con kết thúc bằng select(field("user_name")) hoặc
aggregate(countAll().as("total")) trong đó giản đồ của truy vấn con chỉ là một
trường duy nhất. Nếu không, khi truy vấn con có thể tạo ra nhiều trường, các trường đó sẽ được gói trong một bản đồ.
Sử dụng trình bao bọc SDK toScalarExpression() để chuyển đổi một truy vấn thành một biểu thức vô hướng.
Web
async function toScalarExpressionStageData() {
await setDoc(doc(collection(db, "Authors"), "author_202"), {
"id": "author_202",
"name": "Charles Dickens"
});
await addDoc(collection(db, "Books"), {
"author_id": "author_202",
"title": "Great Expectations",
"rating": 4.8
});
await addDoc(collection(db, "Books"), {
"author_id": "author_202",
"title": "Oliver Twist",
"rating": 4.5
});
}
Đáp
{
"id": "author_202",
"name": "Charles Dickens",
"averageBookRating": 4.65
}
Swift
try await db.collection("authors").document("author_202").setData([ "id": "author_202", "name": "Charles Dickens" ]) try await db.collection("books").document().setData([ "author_id": "author_202", "title": "Great Expectations", "rating": 4.8 ]) try await db.collection("books").document().setData([ "author_id": "author_202", "title": "Oliver Twist", "rating": 4.5 ])
Đáp
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
Kotlin
fun toScalarExpressionData() { val author = hashMapOf( "id" to "author_202", "name" to "Charles Dickens", ) db.collection("Authors").document("author_202").set(author) val book1 = hashMapOf( "author_id" to "author_202", "title" to "Great Expectations", "rating" to 4.8, ) db.collection("Books").add(book1) val book2 = hashMapOf( "author_id" to "author_202", "title" to "Oliver Twist", "rating" to 4.5, ) db.collection("Books").add(book2) }
Đáp
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
Java
public void toScalarExpressionData() { Map<String, Object> author = new HashMap<>(); author.put("id", "author_202"); author.put("name", "Charles Dickens"); db.collection("Authors").document("author_202").set(author); Map<String, Object> book1 = new HashMap<>(); book1.put("author_id", "author_202"); book1.put("title", "Great Expectations"); book1.put("rating", 4.8); db.collection("Books").add(book1); Map<String, Object> book2 = new HashMap<>(); book2.put("author_id", "author_202"); book2.put("title", "Oliver Twist"); book2.put("rating", 4.5); db.collection("Books").add(book2); }
Đáp
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
Truy vấn con subcollection(...)
Mặc dù được cung cấp dưới dạng một giai đoạn, nhưng giai đoạn đầu vào
subcollection(...) cho phép
thực hiện các phép kết hợp trên mô hình dữ liệu phân cấp của Cloud Firestore's. Trong mô hình phân cấp, các truy vấn thường cần truy xuất một tài liệu cùng với dữ liệu từ các bộ sưu tập con của tài liệu đó. Mặc dù bạn có thể thực hiện việc này bằng cách sử dụng giai đoạn đầu vào
collection_group(...) rồi áp dụng bộ lọc cho tham chiếu mẹ, subcollection(...) cung cấp cú pháp ngắn gọn hơn nhiều.
Ngoài điều kiện kết hợp ngầm ẩn, điều này hoạt động tương tự như truy vấn con dạng mảng, trả về kết quả trống nếu không có tài liệu nào được so khớp, ngay cả khi bộ sưu tập lồng nhau không tồn tại.
Về cơ bản, đây là cú pháp dễ hiểu: nó tự động sử dụng __name__ của tài liệu trong phạm vi bên ngoài làm khoá kết hợp để giải quyết mối quan hệ phân cấp. Điều này khiến đây trở thành cách ưu tiên để thực hiện tra cứu trên các bộ sưu tập được liên kết trong mối quan hệ cha mẹ-con.
Các phương pháp hay nhất
- Quản lý bộ nhớ bằng
toArrayExpression(): Hãy thận trọng vớitoArrayExpression()các truy vấn con, vì việc hiện thực hoá một số lượng lớn tài liệu có thể làm cạn kiệt giới hạn bộ nhớ truy vấn (128 MiB). Để giảm thiểu vấn đề này, hãy sử dụngselect(...)trong truy vấn con để chỉ trả về các trường cần thiết và áp dụng bộ lọcwhere(...)để giới hạn số lượng tài liệu được trả về. Cân nhắc sử dụnglimit(...)nếu phù hợp để giới hạn số lượng tài liệu được truy vấn con trả về. - Lập chỉ mục: Đảm bảo rằng các trường được sử dụng trong mệnh đề
where(...)của truy vấn con được lập chỉ mục. Các phép kết hợp hiệu suất cao dựa vào khả năng thực hiện tìm kiếm chỉ mục thay vì quét toàn bộ bảng.
Để biết thêm các phương pháp hay nhất về truy vấn, hãy tham khảo hướng dẫn của chúng tôi về việc tối ưu hoá truy vấn.
Hạn chế
subcollection(...)phạm vi: Giai đoạn đầu vàosubcollection(...)chỉ được hỗ trợ trong các truy vấn con, vì giai đoạn này yêu cầu ngữ cảnh của tài tài liệu mẹ để giải quyết mối quan hệ phân cấp và thực hiện phép kết hợp.- Độ sâu lồng nhau: Bạn có thể lồng các truy vấn con tối đa 20 lớp.
- Mức sử dụng bộ nhớ: Giới hạn 128 MiB đối với dữ liệu được hiện thực hoá áp dụng cho toàn bộ truy vấn, bao gồm cả tất cả tài liệu được kết hợp.