Sto cercando di aggiungere un semplice campo di ricerca, vorrei usare qualcosa di simile
collectionRef.where('name', 'contains', 'searchTerm')
Ho provato a usare where('name', '==', '%searchTerm%')
, ma non ha restituito nulla.
Sto cercando di aggiungere un semplice campo di ricerca, vorrei usare qualcosa di simile
collectionRef.where('name', 'contains', 'searchTerm')
Ho provato a usare where('name', '==', '%searchTerm%')
, ma non ha restituito nulla.
Risposte:
Non c'è tale operatore, quelli ammessi sono ==
, <
, <=
, >
, >=
.
Puoi filtrare solo per prefissi, ad esempio per tutto ciò che inizia tra bar
e foo
che puoi utilizzare
collectionRef.where('name', '>=', 'bar').where('name', '<=', 'foo')
Puoi utilizzare servizi esterni come Algolia o ElasticSearch per questo.
tennis
, ma in base agli operatori di query disponibili non c'è modo di ottenere quei risultati. Combinando >=
e <=
non funziona. Ovviamente posso usare l'Algolia, ma potrei anche usarlo con Firebase per fare la maggior parte delle query e non è necessario passare a Firestore ...
Sebbene la risposta di Kuba sia vera per quanto riguarda le restrizioni, puoi emularla parzialmente con una struttura simile a un set:
{
'terms': {
'reebok': true,
'mens': true,
'tennis': true,
'racket': true
}
}
Ora puoi eseguire query con
collectionRef.where('terms.tennis', '==', true)
Questo funziona perché Firestore creerà automaticamente un indice per ogni campo. Sfortunatamente questo non funziona direttamente per le query composte perché Firestore non crea automaticamente indici compositi.
Puoi ancora aggirare questo problema memorizzando combinazioni di parole, ma diventa brutto velocemente.
Probabilmente stai ancora meglio con una ricerca full text fuoribordo .
where
Sono d'accordo con la risposta di @ Kuba, ma ancora, è necessario aggiungere una piccola modifica per funzionare perfettamente per la ricerca per prefisso. ecco cosa ha funzionato per me
Per la ricerca di record che iniziano con il nome queryText
collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff')
.
Il carattere \uf8ff
utilizzato nella query è un punto di codice molto alto nell'intervallo Unicode (è un codice area di utilizzo privato [PUA]). Poiché è dopo la maggior parte dei caratteri regolari in Unicode, la query corrisponde a tutti i valori che iniziano con queryText
.
Sebbene Firebase non supporti esplicitamente la ricerca di un termine all'interno di una stringa,
Firebase (ora) supporta quanto segue che risolverà il tuo caso e molti altri:
A partire da agosto 2018 supportano le array-contains
query. Vedi: https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html
È ora possibile impostare tutti i termini chiave in un array come un campo, quindi eseguire una query per tutti i documenti che hanno un array che contiene "X". È possibile utilizzare AND logico per effettuare ulteriori confronti per query aggiuntive. (Questo perché firebase attualmente non supporta in modo nativo le query composte per più query contenenti array, quindi le query di ordinamento "AND" dovranno essere eseguite sul client)
L'uso di array in questo stile consentirà loro di essere ottimizzati per scritture simultanee, il che è bello! Non ho verificato che supporti le richieste batch (i documenti non lo dicono) ma scommetto che lo fa poiché è una soluzione ufficiale.
collection("collectionPath").
where("searchTermsArray", "array-contains", "term").get()
Search term
si intende tipicamente un termine intero separato da spazio, punteggiatura, ecc. Su entrambi i lati. Se utilizzi Google in abcde
questo momento, troverai solo risultati per cose come %20abcde.
o ,abcde!
ma non abcdefghijk..
. anche se sicuramente l'intero alfabeto digitato è molto più comune su Internet, la ricerca non è per abcde * è per un abcde isolato
'contains'
, che significa esattamente ciò a cui mi riferisco in molti linguaggi di programmazione. Lo stesso vale '%searchTerm%'
da un punto di vista SQL.
Secondo i documenti di Firestore , Cloud Firestore non supporta l'indicizzazione nativa o la ricerca di campi di testo nei documenti. Inoltre, scaricare un'intera raccolta per cercare i campi lato client non è pratico.
Si consigliano soluzioni di ricerca di terze parti come Algolia ed Elastic Search .
1.) \uf8ff
funziona allo stesso modo di~
2.) È possibile utilizzare una clausola where o clausole start end:
ref.orderBy('title').startAt(term).endAt(term + '~');
è esattamente lo stesso di
ref.where('title', '>=', term).where('title', '<=', term + '~');
3.) No, non funziona se inverti startAt()
e endAt()
in ogni combinazione, tuttavia, puoi ottenere lo stesso risultato creando un secondo campo di ricerca che viene invertito e combinando i risultati.
Esempio: per prima cosa devi salvare una versione invertita del campo quando il campo viene creato. Qualcosa come questo:
// 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);
}
Con questo, puoi cercare le ultime lettere di un campo stringa e le prime , solo lettere centrali o gruppi di lettere casuali. Questo è più vicino al risultato desiderato. Tuttavia, questo non ci aiuterà davvero quando vogliamo lettere o parole centrali casuali. Inoltre, ricorda di salvare tutto in minuscolo o una copia minuscola per la ricerca, quindi le maiuscole non saranno un problema.
4.) Se hai solo poche parole, il Metodo di Ken Tan farà tutto quello che vuoi, o almeno dopo averlo leggermente modificato. Tuttavia, con solo un paragrafo di testo, creerai in modo esponenziale più di 1 MB di dati, che è maggiore del limite di dimensioni del documento di firestore (lo so, l'ho testato).
5.) Se potessi combinare array-contains (o qualche forma di array) con il \uf8ff
trucco, potresti avere una ricerca praticabile che non raggiunge i limiti. Ho provato ogni combinazione, anche con le mappe, e un no go. Se qualcuno lo capisce, pubblicalo qui.
6.) Se devi allontanarti da ALGOLIA e ELASTIC SEARCH, e non ti biasimo affatto, potresti sempre usare mySQL, postSQL o neo4Js su Google Cloud. Sono tutti e 3 facili da configurare e hanno livelli gratuiti. Avresti una funzione cloud per salvare i dati suCreate () e un'altra funzione onCall () per cercare i dati. Semplice ... ish. Allora perché non passare a mySQL? I dati in tempo reale ovviamente! Quando qualcuno scrive DGraph con websocks per dati in tempo reale, contami!
Algolia ed ElasticSearch sono stati creati per essere dbs di sola ricerca, quindi non c'è niente di così veloce ... ma paghi per questo. Google, perché ci allontani da Google e non segui MongoDB noSQL e consenti le ricerche?
AGGIORNAMENTO - HO CREATO UNA SOLUZIONE:
Risposta tardiva ma per chiunque stia ancora cercando una risposta, diciamo che abbiamo una raccolta di utenti e in ogni documento della raccolta abbiamo un campo "nome utente", quindi se vuoi trovare un documento in cui il nome utente inizia con "al" possiamo fare qualcosa di simile
FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")
Sono sicuro che Firebase uscirà presto con "string-contains" per catturare qualsiasi index [i] startAt nella stringa ... Ma ho cercato sul web e ho trovato questa soluzione pensata da qualcun altro che ha impostato i tuoi dati come Questo
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
})
query come questa
firebase
.firestore()
.collection("clubs")
.where(
"titleAsArray",
"array-contains",
this.state.userQuery.toLowerCase()
)
Se non desideri utilizzare un servizio di terze parti come l'Algolia, Firebase Cloud Functions è un'ottima alternativa. È possibile creare una funzione che possa ricevere un parametro di input, elaborare i record lato server e quindi restituire quelli che corrispondono ai criteri.
In realtà penso che la soluzione migliore per farlo all'interno di Firestore sia inserire tutte le sottostringhe in un array e fare semplicemente una query array_contains. Ciò consente di eseguire la corrispondenza delle sottostringhe. Un po 'eccessivo per memorizzare tutte le sottostringhe, ma se i termini di ricerca sono brevi è molto ragionevole.
Ho appena avuto questo problema e ho trovato una soluzione piuttosto semplice.
String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")
IsG majorThanOrEqualTo ci consente di filtrare l'inizio della nostra ricerca e aggiungendo una "z" alla fine di isLessThanOrEqualTo chiudiamo la nostra ricerca per non passare ai documenti successivi.
La risposta selezionata funziona solo per le ricerche esatte e non è un comportamento di ricerca naturale dell'utente (la ricerca di "mela" in "Joe ha mangiato una mela oggi" non funzionerebbe).
Penso che la risposta di Dan Fein sopra dovrebbe essere classificata più in alto. Se i dati della stringa che stai cercando sono brevi, puoi salvare tutte le sottostringhe della stringa in un array nel tuo documento e quindi cercare nell'array con la query array_contains di Firebase. I documenti Firebase sono limitati a 1 MiB (1.048.576 byte) ( quote e limiti di Firebase ), che corrisponde a circa 1 milione di caratteri salvati in un documento (penso 1 carattere ~ = 1 byte). Memorizzare le sottostringhe va bene fintanto che il documento non è vicino a 1 milione di segni.
Esempio per cercare nomi utente:
Passaggio 1: aggiungi la seguente estensione String al tuo progetto. Ciò consente di suddividere facilmente una stringa in sottostringhe. (L' ho trovato qui ).
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])
}
Passaggio 2: quando si memorizza il nome di un utente, memorizzare anche il risultato di questa funzione come un array nello stesso documento. Questo crea tutte le variazioni del testo originale e le memorizza in un array. Ad esempio, l'input di testo "Apple" creerebbe il seguente array: ["a", "p", "p", "l", "e", "ap", "pp", "pl", "le "," app "," ppl "," ple "," appl "," pple "," apple "], che dovrebbero comprendere tutti i criteri di ricerca che un utente potrebbe inserire. Puoi lasciare maximumStringSize come zero se vuoi tutti i risultati, tuttavia, se c'è un testo lungo, ti consiglio di limitarlo prima che la dimensione del documento diventi troppo grande - da qualche parte circa 15 funziona bene per me (la maggior parte delle persone non cerca comunque frasi lunghe ).
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
}
Passaggio 3: puoi utilizzare la funzione array_contains di Firebase!
[yourDatabasePath].whereField([savedSubstringArray], arrayContains: searchText).getDocuments....
Con Firestore puoi implementare una ricerca full text ma costerà comunque più letture di quanto non avrebbe altrimenti, e inoltre dovrai inserire e indicizzare i dati in un modo particolare, quindi in questo approccio puoi utilizzare le funzioni cloud di Firebase per tokenize e quindi hash il testo di input mentre si sceglie una funzione hash lineare h(x)
che soddisfi quanto segue - if x < y < z then h(x) < h (y) < h(z)
. Per la tokenizzazione puoi scegliere alcune librerie NLP leggere per mantenere basso il tempo di avvio a freddo della tua funzione che può eliminare le parole non necessarie dalla tua frase. Quindi puoi eseguire una query con l'operatore minore di e maggiore di in Firestore. Durante la memorizzazione anche dei dati, dovrai assicurarti di eseguire l'hash del testo prima di memorizzarlo e memorizzare il testo normale anche come se cambiassi il testo normale, anche il valore con hash cambierà.
Questo ha funzionato perfettamente per me, ma potrebbe causare problemi di prestazioni.
Fai questo quando esegui una query su Firestore:
Future<QuerySnapshot> searchResults = collectionRef
.where('property', isGreaterThanOrEqualTo: searchQuery.toUpperCase())
.getDocuments();
Fallo nel tuo 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);
})
};
Ad oggi, ci sono fondamentalmente 3 diverse soluzioni alternative, che sono state suggerite dagli esperti, come risposte alla domanda.
Li ho provati tutti. Ho pensato che potesse essere utile documentare la mia esperienza con ognuno di loro.
Metodo-A: utilizzo di: (dbField "> =" searchString) & (dbField "<=" searchString + "\ uf8ff")
Suggerito da @Kuba e @Ankit Prajapati
.where("dbField1", ">=", searchString)
.where("dbField1", "<=", searchString + "\uf8ff");
A.1 Le query Firestore possono eseguire solo filtri di intervallo (>, <,> =, <=) su un singolo campo. Le query con filtri di intervallo su più campi non sono supportate. Utilizzando questo metodo, non è possibile avere un operatore di intervallo in nessun altro campo del database, ad esempio un campo data.
A.2. Questo metodo NON funziona per la ricerca in più campi contemporaneamente. Ad esempio, non puoi controllare se una stringa di ricerca è in uno dei campi (nome, note e indirizzo).
Metodo B: utilizzo di una MAP delle stringhe di ricerca con "true" per ciascuna voce nella mappa e utilizzo dell'operatore "==" nelle query
Suggerito da @Gil Gilbert
document1 = {
'searchKeywordsMap': {
'Jam': true,
'Butter': true,
'Muhamed': true,
'Green District': true,
'Muhamed, Green District': true,
}
}
.where(`searchKeywordsMap.${searchString}`, "==", true);
B.1 Ovviamente, questo metodo richiede un'elaborazione aggiuntiva ogni volta che i dati vengono salvati nel db e, cosa più importante, richiede spazio aggiuntivo per memorizzare la mappa delle stringhe di ricerca.
B.2 Se una query Firestore ha una singola condizione come quella sopra, non è necessario creare alcun indice in anticipo. Questa soluzione funzionerebbe perfettamente in questo caso.
B.3 Tuttavia, se la query ha un'altra condizione, ad esempio (status === "active",) sembra che sia richiesto un indice per ogni "stringa di ricerca" inserita dall'utente. In altre parole, se un utente cerca "Jam" e un altro utente cerca "Butter", è necessario creare prima un indice per la stringa "Jam" e un altro per "Butter", ecc. A meno che tu non possa prevedere tutto il possibile stringhe di ricerca degli utenti, questo NON funziona - nel caso in cui la query abbia altre condizioni!
.where(searchKeywordsMap["Jam"], "==", true); // requires an index on searchKeywordsMap["Jam"]
.where("status", "==", "active");
** Metodo-C: utilizzo di un ARRAY di stringhe di ricerca e dell'operatore "array-contains"
Suggerito da @Albert Renshaw e dimostrato da @Nick Carducci
document1 = {
'searchKeywordsArray': [
'Jam',
'Butter',
'Muhamed',
'Green District',
'Muhamed, Green District',
]
}
.where("searchKeywordsArray", "array-contains", searchString);
C.1 Simile al Metodo-B, questo metodo richiede un'elaborazione aggiuntiva ogni volta che i dati vengono salvati nel database e, cosa più importante, richiede spazio aggiuntivo per memorizzare l'array di stringhe di ricerca.
C.2 Le query Firestore possono includere al massimo una clausola "array-contains" o "array-contains-any" in una query composta.
Limitazioni generali:
Non esiste una soluzione che vada bene per tutti. Ogni soluzione alternativa ha i suoi limiti. Spero che le informazioni di cui sopra possano aiutarti durante il processo di selezione tra queste soluzioni alternative.
Per un elenco delle condizioni delle query Firestore, consulta la documentazione https://firebase.google.com/docs/firestore/query-data/queries .
Non ho provato https://fireblog.io/blog/post/firestore-full-text-search , che è suggerito da @Jonathan.
Possiamo usare il back-tick per stampare il valore di una stringa. Questo dovrebbe funzionare:
where('name', '==', `${searchTerm}`)