Tôi đang tìm cách thêm một trường tìm kiếm đơn giản, tôi muốn sử dụng một thứ gì đó như
collectionRef.where('name', 'contains', 'searchTerm')
Tôi đã thử sử dụng where('name', '==', '%searchTerm%')
, nhưng nó không trả lại bất cứ điều gì.
Tôi đang tìm cách thêm một trường tìm kiếm đơn giản, tôi muốn sử dụng một thứ gì đó như
collectionRef.where('name', 'contains', 'searchTerm')
Tôi đã thử sử dụng where('name', '==', '%searchTerm%')
, nhưng nó không trả lại bất cứ điều gì.
Câu trả lời:
Không có nhà điều hành như vậy, những người cho phép là ==
, <
, <=
, >
, >=
.
Bạn chỉ có thể lọc theo tiền tố, ví dụ: đối với mọi thứ bắt đầu từ giữa bar
và foo
bạn có thể sử dụng
collectionRef.where('name', '>=', 'bar').where('name', '<=', 'foo')
Bạn có thể sử dụng dịch vụ bên ngoài như Algolia hoặc ElasticSearch cho điều đó.
tennis
, nhưng dựa trên các toán tử truy vấn có sẵn, không có cách nào để có được những kết quả đó. Kết hợp >=
và <=
không hoạt động. Dĩ nhiên tôi có thể sử dụng Algolia, nhưng tôi cũng có thể chỉ cần sử dụng nó với căn cứ hỏa lực để làm hầu hết các truy vấn và không cần phải chuyển sang FireStore ...
Mặc dù câu trả lời của Kuba là đúng khi có giới hạn, bạn có thể mô phỏng một phần điều này với cấu trúc giống như một tập hợp:
{
'terms': {
'reebok': true,
'mens': true,
'tennis': true,
'racket': true
}
}
Bây giờ bạn có thể truy vấn với
collectionRef.where('terms.tennis', '==', true)
Điều này hoạt động vì Firestore sẽ tự động tạo chỉ mục cho mọi trường. Thật không may, điều này không hoạt động trực tiếp cho các truy vấn phức hợp vì Firestore không tự động tạo chỉ mục tổng hợp.
Bạn vẫn có thể giải quyết vấn đề này bằng cách lưu trữ các tổ hợp từ nhưng điều này trở nên xấu đi nhanh chóng.
Bạn vẫn có thể tốt hơn với tìm kiếm toàn văn bên ngoài .
where
Tôi đồng ý với câu trả lời của @ Kuba, Tuy nhiên, nó cần phải thêm một thay đổi nhỏ để hoạt động hoàn hảo cho tìm kiếm theo tiền tố. ở đây những gì đã làm việc cho tôi
Để tìm kiếm các bản ghi bắt đầu bằng tên queryText
collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff')
.
Ký tự \uf8ff
được sử dụng trong truy vấn là một điểm mã rất cao trong phạm vi Unicode (nó là mã Khu vực Sử dụng Riêng [PUA]). Vì nó đứng sau hầu hết các ký tự thông thường trong Unicode, nên truy vấn khớp với tất cả các giá trị bắt đầu bằng queryText
.
Mặc dù Firebase không hỗ trợ rõ ràng việc tìm kiếm cụm từ trong một chuỗi,
Firebase hiện (bây giờ) hỗ trợ những điều sau sẽ giải quyết được trường hợp của bạn và nhiều trường hợp khác:
Kể từ tháng 8 năm 2018, họ hỗ trợ array-contains
truy vấn. Xem: https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html
Bây giờ bạn có thể đặt tất cả các thuật ngữ chính của mình thành một mảng dưới dạng một trường, sau đó truy vấn tất cả các tài liệu có một mảng chứa 'X'. Bạn có thể sử dụng logic AND để so sánh thêm cho các truy vấn bổ sung. (Điều này là do firebase hiện không hỗ trợ các truy vấn phức hợp cho nhiều truy vấn chứa mảng vì vậy các truy vấn sắp xếp 'VÀ' sẽ phải được thực hiện ở đầu máy khách)
Sử dụng các mảng theo phong cách này sẽ cho phép chúng được tối ưu hóa để ghi đồng thời, điều này rất hay! Chưa thử nghiệm xem nó có hỗ trợ yêu cầu hàng loạt không (tài liệu không nói) nhưng tôi đánh cuộc nó sẽ làm được vì nó là giải pháp chính thức.
collection("collectionPath").
where("searchTermsArray", "array-contains", "term").get()
Search term
thường được hiểu là một thuật ngữ toàn bộ được phân tách bằng dấu cách, dấu chấm câu, v.v. ở cả hai phía. Nếu bạn google abcde
ngay bây giờ, bạn sẽ chỉ tìm thấy kết quả cho những thứ giống như %20abcde.
hoặc ,abcde!
nhưng không abcdefghijk..
. mặc dù chắc chắn toàn bộ gõ bảng chữ cái được phổ biến hơn nhiều để được tìm thấy trên internet, việc tìm kiếm không phải dành cho abcde * nó cho một abcde bị cô lập
'contains'
, có nghĩa là chính xác những gì tôi đang đề cập đến trong nhiều ngôn ngữ lập trình. Điều tương tự cũng xảy '%searchTerm%'
ra theo quan điểm SQL.
Theo tài liệu của Firestore , Cloud Firestore không hỗ trợ lập chỉ mục gốc hoặc tìm kiếm các trường văn bản trong tài liệu. Ngoài ra, tải xuống toàn bộ bộ sưu tập để tìm kiếm các trường phía máy khách là không thực tế.
Các giải pháp tìm kiếm của bên thứ ba như Algolia và Elastic Search được khuyến nghị.
1.) \uf8ff
hoạt động giống như~
2.) Bạn có thể sử dụng mệnh đề where hoặc mệnh đề bắt đầu kết thúc:
ref.orderBy('title').startAt(term).endAt(term + '~');
giống hệt như
ref.where('title', '>=', term).where('title', '<=', term + '~');
3.) Không, nó không hoạt động nếu bạn đảo ngược startAt()
và endAt()
trong mọi kết hợp, tuy nhiên, bạn có thể đạt được cùng một kết quả bằng cách tạo trường tìm kiếm thứ hai được đảo ngược và kết hợp các kết quả.
Ví dụ: Đầu tiên bạn phải lưu một phiên bản đảo ngược của trường khi trường được tạo. Một cái gì đó như thế này:
// collection
const postRef = db.collection('posts')
async function searchTitle(term) {
// reverse term
const termR = term.split("").reverse().join("");
// define queries
const titles = postRef.orderBy('title').startAt(term).endAt(term + '~').get();
const titlesR = postRef.orderBy('titleRev').startAt(termR).endAt(termR + '~').get();
// get queries
const [titleSnap, titlesRSnap] = await Promise.all([
titles,
titlesR
]);
return (titleSnap.docs).concat(titlesRSnap.docs);
}
Với điều này, bạn có thể tìm kiếm các chữ cái cuối cùng của trường chuỗi và chữ cái đầu tiên , không phải chữ cái giữa hoặc nhóm chữ cái ngẫu nhiên. Điều này gần với kết quả mong muốn hơn. Tuy nhiên, điều này sẽ không thực sự giúp ích cho chúng ta khi chúng ta muốn các chữ cái hoặc từ ở giữa ngẫu nhiên. Ngoài ra, hãy nhớ lưu mọi thứ bằng chữ thường hoặc bản sao chữ thường để tìm kiếm, vì vậy trường hợp không thành vấn đề.
4.) Nếu bạn chỉ có một vài từ, Phương pháp của Ken Tan sẽ làm mọi thứ bạn muốn, hoặc ít nhất là sau khi bạn sửa đổi nó một chút. Tuy nhiên, chỉ với một đoạn văn bản, bạn sẽ tạo ra hơn 1MB dữ liệu theo cấp số nhân, lớn hơn giới hạn kích thước tài liệu của firestore (tôi biết, tôi đã thử nghiệm nó).
5.) Nếu bạn có thể kết hợp mảng chứa (hoặc một số dạng mảng) với \uf8ff
thủ thuật này, bạn có thể có một tìm kiếm khả thi mà không đạt đến giới hạn. Tôi đã thử mọi cách kết hợp, ngay cả với bản đồ và không đi được. Bất cứ ai tìm ra điều này, đăng nó ở đây.
6.) Nếu bạn phải thoát khỏi TÌM KIẾM ALGOLIA và ELASTIC, và tôi không đổ lỗi cho bạn, bạn luôn có thể sử dụng mySQL, postSQL hoặc neo4Js trên Google Cloud. Cả 3 đều dễ thiết lập và có các cấp miễn phí. Bạn sẽ có một hàm đám mây để lưu dữ liệu onCreate () và một hàm onCall () khác để tìm kiếm dữ liệu. Đơn giản ... là được. Tại sao không chuyển sang mySQL sau đó? Tất nhiên là dữ liệu thời gian thực! Khi ai đó viết DGraph bằng websocks để lấy dữ liệu thời gian thực, hãy tính tôi!
Algolia và ElasticSearch được xây dựng để trở thành các dbs chỉ dành cho tìm kiếm, vì vậy, không có gì nhanh bằng ... nhưng bạn phải trả tiền cho nó. Google, tại sao bạn dẫn chúng tôi ra khỏi Google, và bạn không theo dõi MongoDB noSQL và cho phép tìm kiếm?
CẬP NHẬT - TÔI ĐÃ TẠO GIẢI PHÁP:
Câu trả lời muộn nhưng đối với bất kỳ ai vẫn đang tìm kiếm câu trả lời, Giả sử chúng tôi có một tập hợp người dùng và trong mỗi tài liệu của bộ sưu tập, chúng tôi có một trường "tên người dùng", vì vậy nếu muốn tìm tài liệu có tên người dùng bắt đầu bằng "al" chúng ta có thể làm một cái gì đó như
FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")
Tôi chắc chắn rằng Firebase sẽ sớm ra mắt "string-contains" để nắm bắt bất kỳ chỉ mục nào [i] startAt trong chuỗi ... Nhưng tôi đã nghiên cứu các trang web và nhận thấy giải pháp này do người khác nghĩ ra khi thiết lập dữ liệu của bạn như điều này
state = {title:"Knitting"}
...
const c = this.state.title.toLowerCase()
var array = [];
for (let i = 1; i < c.length + 1; i++) {
array.push(c.substring(0, i));
}
firebase
.firestore()
.collection("clubs")
.doc(documentId)
.update({
title: this.state.title,
titleAsArray: array
})
truy vấn như thế này
firebase
.firestore()
.collection("clubs")
.where(
"titleAsArray",
"array-contains",
this.state.userQuery.toLowerCase()
)
Nếu bạn không muốn sử dụng dịch vụ của bên thứ ba như Algolia, thì Firebase Cloud Functions là một sự thay thế tuyệt vời. Bạn có thể tạo một hàm có thể nhận một tham số đầu vào, xử lý thông qua các bản ghi phía máy chủ và sau đó trả về các bản ghi phù hợp với tiêu chí của bạn.
Tôi thực sự nghĩ rằng giải pháp tốt nhất để làm điều này trong Firestore là đặt tất cả các chuỗi con trong một mảng và chỉ thực hiện một truy vấn array_contains. Điều này cho phép bạn thực hiện đối sánh chuỗi con. Hơi quá mức cần thiết để lưu trữ tất cả các chuỗi con nhưng nếu cụm từ tìm kiếm của bạn ngắn thì điều đó rất hợp lý.
Tôi chỉ gặp vấn đề này và đưa ra một giải pháp khá đơn giản.
String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")
IsGreaterThanOrEqualTo cho phép chúng tôi lọc ra phần đầu của tìm kiếm và bằng cách thêm "z" vào cuối isLessThanOrEqualĐể chúng tôi giới hạn tìm kiếm của mình để không chuyển sang các tài liệu tiếp theo.
Câu trả lời đã chọn chỉ hoạt động cho các tìm kiếm chính xác và không phải là hành vi tìm kiếm tự nhiên của người dùng (tìm kiếm "apple" trong "Joe ate an apple today" sẽ không hoạt động).
Tôi nghĩ câu trả lời trên của Dan Fein nên được xếp hạng cao hơn. Nếu dữ liệu Chuỗi mà bạn đang tìm kiếm ngắn, bạn có thể lưu tất cả các chuỗi con của chuỗi vào một mảng trong Tài liệu của mình và sau đó tìm kiếm trong mảng bằng truy vấn array_contains của Firebase. Tài liệu Firebase được giới hạn ở 1 MiB (1.048.576 byte) (Định mức và giới hạn của Firebase ), khoảng 1 triệu ký tự được lưu trong tài liệu (tôi nghĩ 1 ký tự ~ = 1 byte). Lưu trữ các chuỗi con cũng được miễn là tài liệu của bạn không đạt gần mốc 1 triệu.
Ví dụ để tìm kiếm tên người dùng:
Bước 1: Thêm phần mở rộng Chuỗi sau vào dự án của bạn. Điều này cho phép bạn dễ dàng chia một chuỗi thành các chuỗi con. ( Tôi tìm thấy cái này ở đây ).
extension String {
var length: Int {
return count
}
subscript (i: Int) -> String {
return self[i ..< i + 1]
}
func substring(fromIndex: Int) -> String {
return self[min(fromIndex, length) ..< length]
}
func substring(toIndex: Int) -> String {
return self[0 ..< max(0, toIndex)]
}
subscript (r: Range<Int>) -> String {
let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
upper: min(length, max(0, r.upperBound))))
let start = index(startIndex, offsetBy: range.lowerBound)
let end = index(start, offsetBy: range.upperBound - range.lowerBound)
return String(self[start ..< end])
}
Bước 2: Khi bạn lưu trữ tên người dùng, hãy lưu trữ kết quả của hàm này dưới dạng một mảng trong cùng một Tài liệu. Điều này tạo ra tất cả các biến thể của văn bản gốc và lưu trữ chúng trong một mảng. Ví dụ: đầu vào văn bản "Apple" sẽ tạo ra mảng sau: ["a", "p", "p", "l", "e", "ap", "pp", "pl", "le "," app "," ppl "," ple "," appl "," pple "," apple "], phải bao gồm tất cả các tiêu chí tìm kiếm mà người dùng có thể nhập. Bạn có thể để MaximumStringSize là con số không nếu bạn muốn có tất cả kết quả, tuy nhiên, nếu có văn bản dài, tôi khuyên bạn nên giới hạn nó trước khi kích thước tài liệu quá lớn - đối với tôi thì khoảng 15 là tốt (hầu hết mọi người không tìm kiếm các cụm từ dài ).
func createSubstringArray(forText text: String, maximumStringSize: Int?) -> [String] {
var substringArray = [String]()
var characterCounter = 1
let textLowercased = text.lowercased()
let characterCount = text.count
for _ in 0...characterCount {
for x in 0...characterCount {
let lastCharacter = x + characterCounter
if lastCharacter <= characterCount {
let substring = textLowercased[x..<lastCharacter]
substringArray.append(substring)
}
}
characterCounter += 1
if let max = maximumStringSize, characterCounter > max {
break
}
}
print(substringArray)
return substringArray
}
Bước 3: Bạn có thể sử dụng hàm array_contains của Firebase!
[yourDatabasePath].whereField([savedSubstringArray], arrayContains: searchText).getDocuments....
Với Firestore, bạn có thể triển khai tìm kiếm toàn văn nhưng sẽ vẫn tốn nhiều lần đọc hơn so với cách khác và bạn cũng cần nhập và lập chỉ mục dữ liệu theo một cách cụ thể, Vì vậy, trong cách tiếp cận này, bạn có thể sử dụng các chức năng đám mây của firebase để tokenise và sau đó băm văn bản đầu vào của bạn trong khi chọn một hàm băm tuyến tính h(x)
thỏa mãn điều kiện sau - nếu x < y < z then h(x) < h (y) < h(z)
. Đối với mã hóa, bạn có thể chọn một số Thư viện NLP nhẹ để giữ cho thời gian bắt đầu lạnh của chức năng ở mức thấp có thể loại bỏ các từ không cần thiết khỏi câu của bạn. Sau đó, bạn có thể chạy một truy vấn với toán tử nhỏ hơn và lớn hơn trong Firestore. Trong khi lưu trữ dữ liệu của bạn cũng vậy, bạn sẽ phải đảm bảo rằng bạn băm văn bản trước khi lưu trữ nó và lưu trữ văn bản thuần túy cũng giống như nếu bạn thay đổi văn bản thuần túy, giá trị băm cũng sẽ thay đổi.
Điều này hoàn toàn phù hợp với tôi nhưng có thể gây ra các vấn đề về hiệu suất.
Làm điều này khi truy vấn firestore:
Future<QuerySnapshot> searchResults = collectionRef
.where('property', isGreaterThanOrEqualTo: searchQuery.toUpperCase())
.getDocuments();
Làm điều này trong FutureBuilder của bạn:
return FutureBuilder(
future: searchResults,
builder: (context, snapshot) {
List<Model> searchResults = [];
snapshot.data.documents.forEach((doc) {
Model model = Model.fromDocumet(doc);
if (searchQuery.isNotEmpty &&
!model.property.toLowerCase().contains(searchQuery.toLowerCase())) {
return;
}
searchResults.add(model);
})
};
Tính đến ngày hôm nay, về cơ bản có 3 cách giải quyết khác nhau, được các chuyên gia gợi ý, làm câu trả lời cho câu hỏi.
Tôi đã thử tất cả. Tôi nghĩ có thể hữu ích khi ghi lại kinh nghiệm của tôi với từng người trong số họ.
Phương pháp-A: Sử dụng: (dbField "> =" searchString) & (dbField "<=" searchString + "\ uf8ff")
Đề xuất bởi @Kuba & @Ankit Prajapati
.where("dbField1", ">=", searchString)
.where("dbField1", "<=", searchString + "\uf8ff");
A.1 Các truy vấn Firestore chỉ có thể thực hiện các bộ lọc phạm vi (>, <,> =, <=) trên một trường duy nhất. Các truy vấn có bộ lọc phạm vi trên nhiều trường không được hỗ trợ. Bằng cách sử dụng phương pháp này, bạn không thể có toán tử phạm vi trong bất kỳ trường nào khác trên db, ví dụ trường ngày.
A.2. Phương pháp này KHÔNG hoạt động để tìm kiếm trong nhiều trường cùng một lúc. Ví dụ: bạn không thể kiểm tra xem một chuỗi tìm kiếm có nằm trong bất kỳ tệp nào không (tên, ghi chú và địa chỉ).
Phương pháp-B: Sử dụng MAP các chuỗi tìm kiếm với "true" cho mỗi mục nhập trong bản đồ và sử dụng toán tử "==" trong các truy vấn
Do @Gil Gilbert đề xuất
document1 = {
'searchKeywordsMap': {
'Jam': true,
'Butter': true,
'Muhamed': true,
'Green District': true,
'Muhamed, Green District': true,
}
}
.where(`searchKeywordsMap.${searchString}`, "==", true);
B.1 Rõ ràng, phương pháp này yêu cầu xử lý thêm mỗi khi dữ liệu được lưu vào db, và quan trọng hơn, cần thêm không gian để lưu trữ bản đồ các chuỗi tìm kiếm.
B.2 Nếu một truy vấn Firestore có một điều kiện giống như điều kiện ở trên, thì không cần tạo chỉ mục trước. Giải pháp này sẽ hoạt động tốt trong trường hợp này.
B.3 Tuy nhiên, nếu truy vấn có một điều kiện khác, ví dụ (trạng thái === "hoạt động",) thì có vẻ như một chỉ mục được yêu cầu cho mỗi "chuỗi tìm kiếm" mà người dùng nhập vào. Nói cách khác, nếu một người dùng tìm kiếm "Mứt" và một người dùng khác tìm kiếm "Bơ", một chỉ mục nên được tạo trước cho chuỗi "Mứt" và một chỉ mục khác cho "Bơ", v.v. Trừ khi bạn có thể dự đoán tất cả. chuỗi tìm kiếm của người dùng, điều này KHÔNG hoạt động - trong trường hợp truy vấn có các điều kiện khác!
.where(searchKeywordsMap["Jam"], "==", true); // requires an index on searchKeywordsMap["Jam"]
.where("status", "==", "active");
** Phương thức-C: Sử dụng phương pháp ARRAY của các chuỗi tìm kiếm và toán tử "mảng chứa"
Do @Albert Renshaw đề xuất và @Nick Carducci thể hiện
document1 = {
'searchKeywordsArray': [
'Jam',
'Butter',
'Muhamed',
'Green District',
'Muhamed, Green District',
]
}
.where("searchKeywordsArray", "array-contains", searchString);
C.1 Tương tự như Phương pháp-B, phương pháp này yêu cầu xử lý thêm mỗi khi dữ liệu được lưu vào db và quan trọng hơn, yêu cầu thêm không gian để lưu trữ mảng chuỗi tìm kiếm.
C.2 Truy vấn Firestore có thể bao gồm nhiều nhất một mệnh đề "mảng-chứa" hoặc "mảng-chứa-bất kỳ" trong một truy vấn ghép.
Hạn chế chung:
Không có một giải pháp phù hợp với tất cả. Mỗi cách giải quyết đều có những hạn chế của nó. Tôi hy vọng thông tin trên có thể giúp bạn trong quá trình lựa chọn giữa các cách giải quyết này.
Để biết danh sách các điều kiện truy vấn Firestore, vui lòng xem tài liệu https://firebase.google.com/docs/firestore/query-data/queries .
Tôi chưa thử https://fireblog.io/blog/post/firestore-full-text-search , được gợi ý bởi @Jonathan.
Chúng ta có thể sử dụng dấu tích để in ra giá trị của một chuỗi. Điều này sẽ hoạt động:
where('name', '==', `${searchTerm}`)