Chcę dodać proste pole wyszukiwania, chciałbym użyć czegoś takiego
collectionRef.where('name', 'contains', 'searchTerm')
Próbowałem użyć where('name', '==', '%searchTerm%')
, ale nic nie zwróciło.
Chcę dodać proste pole wyszukiwania, chciałbym użyć czegoś takiego
collectionRef.where('name', 'contains', 'searchTerm')
Próbowałem użyć where('name', '==', '%searchTerm%')
, ale nic nie zwróciło.
Odpowiedzi:
Nie ma takiego operatora, dozwolone są te ==
, <
, <=
, >
, >=
.
Możesz filtrować tylko według prefiksów, na przykład dla wszystkiego, co zaczyna się od bar
i foo
możesz użyć
collectionRef.where('name', '>=', 'bar').where('name', '<=', 'foo')
W tym celu możesz skorzystać z usług zewnętrznych, takich jak Algolia lub ElasticSearch.
tennis
, ale na podstawie dostępnych operatorów zapytań nie ma możliwości uzyskania tych wyników. Łączenie >=
i <=
nie działa. Oczywiście mogę używać Algolii, ale mógłbym też po prostu używać go z Firebase do wykonywania większości zapytań i nie muszę przełączać się na Firestore ...
Zgadzam się z odpowiedzią @ Kuba, ale mimo to musi dodać małą zmianę, aby działało idealnie przy wyszukiwaniu według prefiksu. tutaj, co zadziałało dla mnie
Do wyszukiwania rekordów zaczynających się od nazwy queryText
collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff')
.
Znak \uf8ff
użyty w zapytaniu to bardzo wysoki punkt kodowy w zakresie Unicode (jest to kod prywatnego obszaru użytkowania [PUA]). Ponieważ występuje po większości zwykłych znaków w standardzie Unicode, zapytanie dopasowuje wszystkie wartości zaczynające się od queryText
.
Chociaż odpowiedź Kuby jest prawdziwa, jeśli chodzi o ograniczenia, możesz częściowo naśladować to za pomocą struktury podobnej do zestawu:
{
'terms': {
'reebok': true,
'mens': true,
'tennis': true,
'racket': true
}
}
Teraz możesz zapytać za pomocą
collectionRef.where('terms.tennis', '==', true)
Działa to, ponieważ Firestore automatycznie utworzy indeks dla każdego pola. Niestety nie działa to bezpośrednio w przypadku zapytań złożonych, ponieważ Firestore nie tworzy automatycznie indeksów złożonych.
Nadal możesz obejść ten problem, przechowując kombinacje słów, ale szybko robi się to brzydko.
Prawdopodobnie lepiej będzie, jeśli korzystasz z zewnętrznego wyszukiwania pełnotekstowego .
where
Chociaż Firebase nie obsługuje wprost wyszukiwania hasła w ciągu,
Firebase obsługuje (teraz) następujące elementy, które rozwiążą Twój przypadek i wiele innych:
Od sierpnia 2018 obsługują array-contains
zapytania. Widzieć: https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html
Możesz teraz ustawić wszystkie kluczowe terminy w tablicy jako pole, a następnie zapytać o wszystkie dokumenty, które mają tablicę zawierającą „X”. Możesz użyć logicznego AND, aby dokonać dalszych porównań dodatkowych zapytań. (Dzieje się tak, ponieważ Firebase nie obsługuje obecnie natywnie zapytań złożonych dla wielu zapytań zawierających tablicę więc zapytania sortujące „AND” będą musiały być wykonywane po stronie klienta)
Używanie tablic w tym stylu pozwoli na ich optymalizację pod kątem współbieżnych zapisów, co jest miłe! Nie testowałem, czy obsługuje żądania wsadowe (dokumenty nie mówią), ale założę się, że tak, ponieważ jest to oficjalne rozwiązanie.
collection("collectionPath").
where("searchTermsArray", "array-contains", "term").get()
Search term
zazwyczaj oznacza cały termin oddzielony spacją, znakami interpunkcyjnymi itp. Po obu stronach. Jeśli wyszukasz abcde
teraz w Google , znajdziesz tylko wyniki dla rzeczy takich jak %20abcde.
lub, ,abcde!
ale nie abcdefghijk..
. chociaż z pewnością cały wpisany alfabet jest znacznie bardziej powszechny w Internecie, wyszukiwanie nie dotyczy abcde *, ale izolowanego abcde
'contains'
, które oznacza dokładnie to, o czym mówię w wielu językach programowania. To samo dotyczy '%searchTerm%'
z punktu widzenia SQL.
Zgodnie z dokumentacją Firestore , Cloud Firestore nie obsługuje natywnego indeksowania ani wyszukiwania pól tekstowych w dokumentach. Ponadto pobranie całej kolekcji w celu wyszukania pól po stronie klienta nie jest praktyczne.
Zalecane są rozwiązania wyszukiwania innych firm, takie jak Algolia i Elastic Search .
1.) \uf8ff
działa tak samo jak~
2.) Możesz użyć klauzuli where lub klauzuli początku końca:
ref.orderBy('title').startAt(term).endAt(term + '~');
jest dokładnie taki sam jak
ref.where('title', '>=', term).where('title', '<=', term + '~');
3.) Nie, to nie zadziała, jeśli odwrócisz startAt()
i endAt()
we wszystkich kombinacjach, jednak możesz osiągnąć ten sam wynik, tworząc drugie pole wyszukiwania, które jest odwrócone, i łącząc wyniki.
Przykład: Najpierw musisz zapisać odwróconą wersję pola podczas tworzenia pola. Coś takiego:
// 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);
}
Dzięki temu możesz przeszukiwać ostatnie litery pola tekstowego i pierwsze , ale nie losowe, środkowe litery lub grupy liter. Jest to bliższe pożądanemu rezultatowi. Jednak to nie pomoże nam, gdy chcemy losowych środkowych liter lub słów. Pamiętaj też, aby zapisać wszystkie małe litery lub małą kopię do wyszukiwania, więc wielkość liter nie będzie problemem.
4.) Jeśli masz tylko kilka słów, metoda Kena Tana zrobi wszystko, co chcesz, lub przynajmniej po nieznacznej modyfikacji. Jednak mając tylko akapit tekstu, utworzysz wykładniczo więcej niż 1 MB danych, czyli więcej niż limit rozmiaru dokumentu w Firestore (wiem, przetestowałem to).
5.) Gdybyś mógł połączyć zawartość tablicy (lub jakąś formę tablic) ze \uf8ff
sztuczką, mógłbyś mieć wykonalne wyszukiwanie, które nie osiąga limitów. Próbowałem każdej kombinacji, nawet z mapami, i nie mogłem iść. Każdy, kto to wymyśli, umieść tutaj.
6.) Jeśli musisz uciec od ALGOLIA i ELASTIC SEARCH, a ja cię wcale nie winię, zawsze możesz użyć mySQL, postSQL lub neo4Js w Google Cloud. Wszystkie są łatwe do skonfigurowania i mają darmowe poziomy. Będziesz mieć jedną funkcję w chmurze do zapisywania danych onCreate () i drugą funkcję onCall () do wyszukiwania danych. Proste ... ish. Dlaczego więc po prostu nie przełączyć się na mySQL? Oczywiście dane w czasie rzeczywistym! Kiedy ktoś pisze DGraph z websocks w celu uzyskania danych w czasie rzeczywistym, policz mnie!
Algolia i ElasticSearch zostały zbudowane jako bazy danych tylko do wyszukiwania, więc nie ma nic tak szybkiego ... ale za to płacisz. Google, dlaczego odciągasz nas od Google i nie śledzisz MongoDB noSQL i nie zezwalasz na wyszukiwanie?
AKTUALIZACJA - STWORZYŁEM ROZWIĄZANIE:
Spóźniona odpowiedź, ale dla każdego, kto wciąż szuka odpowiedzi, Powiedzmy, że mamy zbiór użytkowników i w każdym dokumencie z kolekcji mamy pole „nazwa użytkownika”, więc jeśli chcesz znaleźć dokument, w którym nazwa użytkownika zaczyna się od „al” możemy zrobić coś takiego
FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")
Jestem pewien, że Firebase wkrótce wyjdzie z „ciągiem-zawiera”, aby przechwycić dowolny indeks [i] startAt w ciągu znaków ... Ale przeszukałem sieci i znalazłem to rozwiązanie, o którym pomyślał ktoś inny, konfigurując Twoje dane, np. to
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
})
zapytanie w ten sposób
firebase
.firestore()
.collection("clubs")
.where(
"titleAsArray",
"array-contains",
this.state.userQuery.toLowerCase()
)
Jeśli nie chcesz korzystać z usługi innej firmy, takiej jak Algolia, Firebase Cloud Functions to świetna alternatywa. Możesz utworzyć funkcję, która może odbierać parametr wejściowy, przetwarzać rekordy po stronie serwera, a następnie zwracać te, które spełniają Twoje kryteria.
Wybrana odpowiedź działa tylko w przypadku dokładnych wyszukiwań i nie jest naturalnym zachowaniem użytkownika podczas wyszukiwania (wyszukiwanie hasła „jabłko” w „Joe zjadł jabłko dzisiaj” nie zadziała).
Myślę, że powyższa odpowiedź Dana Feina powinna być wyżej oceniona. Jeśli przeszukiwane dane String są krótkie, możesz zapisać wszystkie podciągi ciągu w tablicy w swoim dokumencie, a następnie przeszukać tablicę za pomocą zapytania array_contains Firebase. Dokumenty Firebase są ograniczone do 1 MiB (1048576 bajtów) ( przydziały i limity Firebase ), czyli około 1 miliona znaków zapisanych w dokumencie (myślę, że 1 znak ~ = 1 bajt). Przechowywanie podciągów jest w porządku, o ile dokument nie jest bliski 1 milionowi znaków.
Przykład wyszukiwania nazw użytkowników:
Krok 1: Dodaj następujące rozszerzenie ciągu do projektu. Pozwala to łatwo podzielić ciąg na podciągi. ( Znalazłem to tutaj ).
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])
}
Krok 2: Kiedy przechowujesz nazwę użytkownika, zapisz wynik tej funkcji również jako tablicę w tym samym dokumencie. Spowoduje to utworzenie wszystkich odmian oryginalnego tekstu i zapisanie ich w tablicy. Na przykład wprowadzenie tekstu „Apple” utworzy następującą tablicę: [„a”, „p”, „p”, „l”, „e”, „ap”, „pp”, „pl”, „le „,„ aplikacja ”,„ ppl ”,„ ple ”,„ aplikacja ”,„ pple ”,„ jabłko ”], które powinny obejmować wszystkie kryteria wyszukiwania, które może wprowadzić użytkownik. Możesz zostawić wartość maximumStringSize na zero, jeśli chcesz uzyskać wszystkie wyniki, jednak jeśli jest długi tekst, zalecałbym ograniczenie go, zanim rozmiar dokumentu stanie się zbyt duży - gdzieś około 15 działa dobrze dla mnie (większość ludzi i tak nie szuka długich fraz ).
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
}
Krok 3: Możesz użyć funkcji array_contains Firebase!
[yourDatabasePath].whereField([savedSubstringArray], arrayContains: searchText).getDocuments....
Właściwie myślę, że najlepszym rozwiązaniem do tego w Firestore jest umieszczenie wszystkich podciągów w tablicy i po prostu wykonanie zapytania array_contains. Pozwala to na dopasowanie podciągów. Przechowywanie wszystkich podciągów jest trochę przesadzone, ale jeśli wyszukiwane hasła są krótkie, jest to bardzo rozsądne.
Właśnie miałem ten problem i wpadłem na całkiem proste rozwiązanie.
String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")
Funkcja isGreaterThanOrEqualTo pozwala nam odfiltrować początek naszego wyszukiwania i dodając „z” na końcu isLessThanOrEqualTo, abyśmy ograniczyli nasze wyszukiwanie do następnych dokumentów.
Dzięki Firestore możesz wdrożyć wyszukiwanie pełnotekstowe, ale nadal będzie to kosztować więcej odczytów niż w innym przypadku, a także będziesz musiał wprowadzać i indeksować dane w określony sposób, więc w tym podejściu możesz użyć funkcji chmury Firebase do tokenizuj, a następnie haszuj tekst wejściowy, wybierając liniową funkcję skrótu, h(x)
która spełnia następujące warunki - jeśli x < y < z then h(x) < h (y) < h(z)
. Do tokenizacji możesz wybrać kilka lekkich bibliotek NLP, aby skrócić czas zimnego rozpoczęcia funkcji, co może usunąć niepotrzebne słowa z zdania. Następnie możesz uruchomić zapytanie z operatorem mniejsze niż i większe niż w Firestore. Przechowując również swoje dane, musisz upewnić się, że haszujesz tekst przed jego zapisaniem i przechowywać zwykły tekst, tak jakbyś zmienił zwykły tekst, zaszyfrowana wartość również się zmieni.
To działało idealnie, ale może powodować problemy z wydajnością.
Zrób to podczas wysyłania zapytań do Firestore:
Future<QuerySnapshot> searchResults = collectionRef
.where('property', isGreaterThanOrEqualTo: searchQuery.toUpperCase())
.getDocuments();
Zrób to w swoim FutureBuilder:
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);
})
};
Na dzień dzisiejszy istnieją zasadniczo 3 różne obejścia, które eksperci zasugerowali jako odpowiedzi na pytanie.
Wypróbowałem je wszystkie. Pomyślałem, że przydatne może być udokumentowanie moich doświadczeń z każdym z nich.
Metoda A: użycie: (dbField "> =" searchString) & (dbField "<=" searchString + "\ uf8ff")
Sugerowane przez @Kuba i @Ankit Prajapati
.where("dbField1", ">=", searchString)
.where("dbField1", "<=", searchString + "\uf8ff");
A.1 Zapytania Firestore mogą wykonywać filtry zakresu (>, <,> =, <=) tylko na jednym polu. Zapytania z filtrami zakresu w wielu polach nie są obsługiwane. Używając tej metody, nie możesz mieć operatora zakresu w żadnym innym polu bazy danych, np. Polu daty.
A.2. Ta metoda NIE działa w przypadku wyszukiwania w wielu polach jednocześnie. Na przykład nie możesz sprawdzić, czy ciąg wyszukiwania znajduje się w żadnym z pól (nazwa, notatki i adres).
Metoda-B: Użycie MAP wyszukiwania ciągów z wartością „prawda” dla każdego wpisu na mapie i użycie operatora „==” w zapytaniach
Zaproponowane przez @Gil Gilbert
document1 = {
'searchKeywordsMap': {
'Jam': true,
'Butter': true,
'Muhamed': true,
'Green District': true,
'Muhamed, Green District': true,
}
}
.where(`searchKeywordsMap.${searchString}`, "==", true);
B.1 Oczywiście ta metoda wymaga dodatkowego przetwarzania za każdym razem, gdy dane są zapisywane w bazie danych, a co ważniejsze, wymaga dodatkowej przestrzeni do przechowywania mapy ciągów wyszukiwania.
B.2 Jeśli zapytanie Firestore ma pojedynczy warunek, taki jak powyższy, nie trzeba wcześniej tworzyć indeksu. To rozwiązanie działałoby dobrze w tym przypadku.
B.3 Jeśli jednak zapytanie ma inny warunek, np. (Status === "aktywny"), wydaje się, że wymagany jest indeks dla każdego "ciągu wyszukiwania" wprowadzanego przez użytkownika. Innymi słowy, jeśli użytkownik wyszukuje „dżem”, a inny użytkownik wyszukuje „masło”, należy wcześniej utworzyć indeks dla ciągu znaków „dżem”, a drugi dla „masła” itp. O ile nie można przewidzieć wszystkich możliwych ciągi wyszukiwania użytkowników, to NIE działa - w przypadku zapytania ma inne warunki!
.where(searchKeywordsMap["Jam"], "==", true); // requires an index on searchKeywordsMap["Jam"]
.where("status", "==", "active");
** Metoda-C: Użycie ARRAY wyszukiwanych ciągów i operatora „tablica-zawiera”
Zaproponowane przez @Albert Renshaw i zademonstrowane przez @Nick Carducci
document1 = {
'searchKeywordsArray': [
'Jam',
'Butter',
'Muhamed',
'Green District',
'Muhamed, Green District',
]
}
.where("searchKeywordsArray", "array-contains", searchString);
C.1 Podobnie jak w metodzie B, ta metoda wymaga dodatkowego przetwarzania za każdym razem, gdy dane są zapisywane w bazie danych, a co ważniejsze, wymaga dodatkowej przestrzeni do przechowywania tablicy ciągów wyszukiwania.
C.2 Zapytania Firestore mogą zawierać co najwyżej jedną klauzulę „tablica-zawiera” lub „tablica-zawiera-dowolną” w zapytaniu złożonym.
Ogólne ograniczenia:
Nie ma jednego rozwiązania pasującego do wszystkich. Każde obejście ma swoje ograniczenia. Mam nadzieję, że powyższe informacje okażą się pomocne podczas procesu wyboru między tymi obejściami.
Listę warunków zapytań Firestore znajdziesz w dokumentacji https://firebase.google.com/docs/firestore/query-data/queries .
Nie próbowałem https://fireblog.io/blog/post/firestore-full-text-search , co sugeruje @Jonathan.
Możemy użyć back-tick, aby wydrukować wartość ciągu. To powinno działać:
where('name', '==', `${searchTerm}`)