Google Firestore: zapytanie dotyczące podłańcucha wartości właściwości (wyszukiwanie tekstowe)

106

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.

tehfailsafe
źródło
1
Firebase teraz to obsługuje. Zaktualizuj odpowiedź: stackoverflow.com/a/52715590/2057171
Albert Renshaw
1
Myślę, że najlepszym sposobem jest stworzenie skryptu, który ręcznie indeksuje każdy dokument. Następnie odpytaj te indeksy. sprawdź to: angularfirebase.com/lessons/…
Kiblawi_Rabee

Odpowiedzi:

37

Nie ma takiego operatora, dozwolone są te ==, <, <=, >, >=.

Możesz filtrować tylko według prefiksów, na przykład dla wszystkiego, co zaczyna się od bari foomoż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.

Kuba
źródło
5
Nie do końca tego szukam. Mam dużą listę produktów z długimi tytułami. „Rebok Mens Tennis Racket”. Użytkownik może wyszukiwać 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 ...
tehfailsafe
4
@tehfailsafe Cóż, twoje pytanie brzmi „jak zapytać, czy pole zawiera ciąg”, a odpowiedź brzmi „nie możesz tego zrobić”.
Kuba
20
@ A.Chakroun, co właściwie jest niegrzeczne w mojej odpowiedzi?
Kuba
19
tbh to jest coś naprawdę potrzebnego. Nie rozumiem, dlaczego zespół Firebase o tym nie pomyślał
Dani
2
Naprawdę zaskoczę, że Firebase jest tak słaby w zapytaniach. Nie mogę uwierzyć, że używa go tak wielu ludzi, jeśli nie obsługuje tak prostego zapytania.
Bagusflyer
45

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 \uf8ffuż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.

Ankit Prajapati
źródło
1
Dobra odpowiedź !, to świetnie sprawdza się przy wyszukiwaniu tekstu przedrostka. Aby wyszukać słowa w tekście, możesz spróbować implementacji „tablica-zawiera”, jak wyjaśniono w tym poście medium.com/@ken11zer01/ ...
guillefd
Pomyśl tylko, ale teoretycznie możesz dopasować wszystkie wartości kończące się na queryTest , tworząc kolejne pole i odwracając dane ...
Jonathan
Tak @Jonathan, to też jest możliwe.
Ankit Prajapati
43

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 .

Gil Gilbert
źródło
Czy można korzystać z funkcji chmury w cloud.google.com/appengine/docs/standard/java/search ?
Henry
1
Jeśli pytasz o to jako kontynuację tej odpowiedzi, to: Pełnotekstowe wyszukiwanie w AppEngine jest całkowicie niezależne od Firestore, więc nie pomoże ci to bezpośrednio. Możesz replikować swoje dane za pomocą funkcji chmury, ale zasadniczo na tym polega sugestia korzystania z zewnętrznego wyszukiwania pełnotekstowego. Jeśli pytasz o coś innego, zacznij nowe pytanie.
Gil Gilbert,
1
W Firestore musisz zindeksować wszystkie terminy przed użyciemwhere
Husam
4
Jak wspomniał Husam, wszystkie te pola musiałyby zostać zindeksowane. Chciałem umożliwić wyszukiwanie dowolnego terminu, który zawiera moja nazwa produktu. Utworzyłem więc właściwość typu „obiekt” w moim dokumencie z kluczami będącymi częściami nazwy produktu, z których każdy ma przypisaną wartość „true”, mając nadzieję, że wyszukiwanie gdzie („nameSegments.tennis”, „==”, true) work, ale firestore sugeruje utworzenie indeksu dla nameSegments.tennis, takiego samego dla każdego innego terminu. Ponieważ może istnieć nieskończona liczba terminów, ta odpowiedź jest użyteczna tylko w bardzo ograniczonym scenariuszu, gdy wszystkie wyszukiwane terminy są zdefiniowane z góry.
Sławoj
2
@epeleg Zapytanie zadziałałoby po utworzeniu dla niego indeksu, ale nie jest możliwe utworzenie indeksu dla każdego możliwego terminu, który zawiera nazwa produktu, więc w przypadku wyszukiwania tekstowego terminów w nazwach produktów to podejście nie zadziałało w moim przypadku.
Slawoj
30

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-containszapytania. 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.


Stosowanie:

collection("collectionPath").
    where("searchTermsArray", "array-contains", "term").get()
Albert Renshaw
źródło
12
To fajne rozwiązanie. Jednak popraw mnie, jeśli się mylę, ale myślę, że nie pozwala to zrobić tego, o co prosił @tehfailsafe. Np. Jeśli chcesz otrzymać wszystkie nazwy zawierające ciąg „abc”, nie odniesiesz żadnego sukcesu z zawartością tablicy, ponieważ zwróci tylko dokumenty, które mają dokładną nazwę „abc”, ale „abcD” lub „0abc” byłoby wyłączone.
Julian
1
@Yulian W świecie programowania Search termzazwyczaj oznacza cały termin oddzielony spacją, znakami interpunkcyjnymi itp. Po obu stronach. Jeśli wyszukasz abcdeteraz 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
Albert Renshaw
1
Rozumiem twój punkt widzenia i zgadzam się z tym, ale prawdopodobnie zmyliło mnie to słowo 'contains', które oznacza dokładnie to, o czym mówię w wielu językach programowania. To samo dotyczy '%searchTerm%'z punktu widzenia SQL.
Julian
2
@Yulian Yeah, rozumiem. Firebase jest jednak NoSQL, więc jest naprawdę dobry w tworzeniu szybkich i wydajnych tego typu operacji, nawet jeśli mogą być one ograniczone w przypadku niektórych problemów spoza zakresu, takich jak wyszukiwanie ciągów znaków wieloznacznych.
Albert Renshaw,
2
Cóż, możesz utworzyć oddzielne pole dla każdego z nich z reprezentacją słów podzielonych, na przykład tablica tytułowa: ['this', 'is', 'a', 'title'] za każdym razem, gdy aktualizujesz dokument. A potem wyszukiwanie będzie oparte na tym polu zamiast na tytule. Zimno tworzysz triiger onUpdate, aby utworzyć te pola. Dużo pracy dla tekstu opartego na wyszukiwaniu, ale wolę ulepszenia wydajności NoSQL.
sfratini
15

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 .

bholben
źródło
46
Przeczytałem dokumenty, chociaż nie są one idealne. Wadą jest to, że Algolia i Firestore mają różne modele cenowe ... Na szczęście mogę mieć 600 000 dokumentów w Firestore (o ile nie sprawdzam zbyt wielu dokumentów dziennie). Kiedy wysyłam je do Algolii w celu wyszukiwania, muszę teraz płacić Algolii 310 USD miesięcznie tylko po to, aby móc przeszukać moje dokumenty w Firestore.
tehfailsafe
2
problem w tym, że to nie jest darmowe
Dani
To jest prawidłowa odpowiedź na postawione pytanie i należy ją przyjąć jako najlepszą.
briznad
13

Kilka uwag:

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 \uf8ffsztuczką, 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:

https://fireblog.io/blog/post/firestore-full-text-search

Jonathan
źródło
Świetny przegląd i bardzo pomocny.
RedFilter
Niesamowite! głosuj za dobrze zorganizowaną i pouczającą odpowiedzią.
King Of The Jungle
10

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")
MoTahir
źródło
to jest świetne, łatwe rozwiązanie, dziękuję. Ale co, jeśli chcesz sprawdzić więcej niż jedno pole. Na przykład „nazwa” i „opis” połączone operatorem?
Wypróbuj
Nie sądzę, że możesz wyszukiwać w oparciu o dwa pola, niestety Firebase jest zły, jeśli chodzi o zapytania, możesz sprawdzić tę nadzieję, że pomaga stackoverflow.com/questions/26700924/ ...
MoTahir
1
Potwierdzone, @MoTahir. W Firestore nie ma „LUB”.
Rap
to rozwiązanie nie pasuje do nazw użytkowników zaczynających się od „al” ... na przykład zostanie dopasowane „hello” („hello”> „al”)
antoine129
Zapytanie przez OR to po prostu kwestia połączenia dwóch wyników wyszukiwania. Sortowanie wyników to inny problem ...
Jonathan
7

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

wprowadź opis obrazu tutaj

zapytanie w ten sposób

firebase
.firestore()
.collection("clubs")
.where(
 "titleAsArray",
 "array-contains",
 this.state.userQuery.toLowerCase()
)
Nick Carducci
źródło
W ogóle nie jest zalecane. Ponieważ dokumenty mają limit 20 tysięcy wierszy, po prostu nie możesz go wykorzystać w ten sposób, dopóki nie masz pewności, że twój dokument nigdy nie osiągnie takich limitów
Sandeep
To najlepsza opcja w tej chwili, co jeszcze jest zalecane?
Nick Carducci
1
@Sandeep Jestem prawie pewien, że rozmiar jest ograniczony do 1 MB rozmiaru i 20 poziomów głębokości na dokument. Co masz na myśli mówiąc o 20 000 linii? Jest to obecnie najlepsze obejście, jeśli użycie Algolia lub ElasticSearch jest poza tabelą
ppicom
6

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.

Kuks
źródło
1
A co z Androidem?
Pratik Butani
Czy proponujesz, aby ludzie powtarzali każdy rekord w kolekcji?
DarkNeuron
Nie całkiem. Użyłbym Array.prototype. * - Like .every (), .some (), .map (), .filter (), itd. Jest to robione w węźle na serwerze w funkcji Firebase przed zwróceniem wartości do klient.
Rap
3
Nadal musiałbyś przeczytać WSZYSTKIE dokumenty, aby je przeszukać, co wiąże się z kosztami i jest kosztowne dla czasu.
Jonathan
4

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....
purebreadd
źródło
3

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.

Dan Fein
źródło
2

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.

Jacob Bonk
źródło
3
Wypróbowałem to rozwiązanie, ale dla mnie działa tylko wtedy, gdy zostanie wprowadzony pełny ciąg. Na przykład, jeśli chcę uzyskać termin „bezpłatny”, jeśli zacznę wpisywać „fr”, nic nie zwróci. Gdy wpiszę „za darmo”, to termin daje mi migawkę.
Chris
Czy używasz tego samego formatu kodu? A czy termin jest ciągiem w Firestore? Wiem, że nie możesz filtrować według documentId.
Jacob Bonk
0

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.

Adarsh ​​Srivastava
źródło
0

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);
            })
   };
Arun Yogeshwaran
źródło
0

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:

  1. Żadne z tych rozwiązań nie wydaje się wspierać wyszukiwania częściowych ciągów. Na przykład, jeśli pole db zawiera „1 Peter St, Green District”, nie możesz wyszukać ciągu „ścisłe”.
  2. Objęcie wszystkich możliwych kombinacji oczekiwanych ciągów wyszukiwania jest prawie niemożliwe. Na przykład, jeśli pole db zawiera „1 Mohamed St, Green District”, możesz NIE być w stanie wyszukać ciągu „Green Mohamed”, który jest ciągiem zawierającym słowa w innej kolejności niż kolejność użyta w bazie danych pole.

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.

Bilal Abdeen
źródło
-10

Możemy użyć back-tick, aby wydrukować wartość ciągu. To powinno działać:

where('name', '==', `${searchTerm}`)
Zach J
źródło
Dzięki, ale to pytanie dotyczy uzyskania niedokładnych wartości. Na przykład, podany przykład sprawdza, czy nazwa jest dokładna. Jeśli mam dokument o nazwie: „Test”, a następnie wyszukuję „Test”, to działa. Ale spodziewałbym się, że będę mógł wyszukać „tes” lub „est” i nadal uzyskać wynik „Test”. Wyobraź sobie przypadek użycia z tytułami książek. Ludzie często szukają częściowych tytułów książek, zamiast dokładnego tytułu.
tehfailsafe
13
@suulisin masz rację, nie przeczytałem go uważnie, ponieważ chciałem podzielić się tym, co znalazłem. Dziękuję za wysiłek, aby to wskazać, i będę bardziej ostrożny
Zach J