Je cherche à ajouter un champ de recherche simple, j'aimerais utiliser quelque chose comme
collectionRef.where('name', 'contains', 'searchTerm')
J'ai essayé d'utiliser where('name', '==', '%searchTerm%')
, mais cela n'a rien retourné.
Je cherche à ajouter un champ de recherche simple, j'aimerais utiliser quelque chose comme
collectionRef.where('name', 'contains', 'searchTerm')
J'ai essayé d'utiliser where('name', '==', '%searchTerm%')
, mais cela n'a rien retourné.
Réponses:
Il n'y a pas tel opérateur, les permis sont ==
, <
, <=
, >
, >=
.
Vous pouvez filtrer par préfixes uniquement, par exemple pour tout ce qui commence entre bar
et foo
vous pouvez utiliser
collectionRef.where('name', '>=', 'bar').where('name', '<=', 'foo')
Vous pouvez utiliser un service externe comme Algolia ou ElasticSearch pour cela.
tennis
, mais en fonction des opérateurs de requête disponibles, il n'y a aucun moyen d'obtenir ces résultats. Combiner >=
et <=
ne fonctionne pas. Bien sûr, je peux utiliser Algolia, mais je pourrais aussi l'utiliser avec Firebase pour faire la plupart des requêtes sans avoir besoin de passer à Firestore ...
Je suis d'accord avec la réponse de @ Kuba, mais il faut quand même ajouter un petit changement pour fonctionner parfaitement pour la recherche par préfixe. voici ce qui a fonctionné pour moi
Pour rechercher des enregistrements commençant par le nom queryText
collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff')
.
Le caractère \uf8ff
utilisé dans la requête est un point de code très élevé dans la plage Unicode (il s'agit d'un code de zone d'utilisation privée [PUA]). Comme elle se trouve après la plupart des caractères normaux en Unicode, la requête correspond à toutes les valeurs commençant par queryText
.
Bien que la réponse de Kuba soit vraie en ce qui concerne les restrictions, vous pouvez partiellement émuler cela avec une structure semblable à un ensemble:
{
'terms': {
'reebok': true,
'mens': true,
'tennis': true,
'racket': true
}
}
Vous pouvez maintenant interroger avec
collectionRef.where('terms.tennis', '==', true)
Cela fonctionne car Firestore créera automatiquement un index pour chaque champ. Malheureusement, cela ne fonctionne pas directement pour les requêtes composées car Firestore ne crée pas automatiquement d'index composites.
Vous pouvez toujours contourner ce problème en stockant des combinaisons de mots, mais cela devient très rapide.
Vous êtes probablement toujours mieux avec une recherche de texte intégral hors-bord .
where
Alors que Firebase ne prend pas explicitement en charge la recherche d'un terme dans une chaîne,
Firebase prend (maintenant) en charge les éléments suivants qui résoudront votre cas et bien d'autres:
Depuis août 2018, ils prennent en charge la array-contains
requête. Voir: https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html
Vous pouvez maintenant définir tous vos termes clés dans un tableau en tant que champ, puis rechercher tous les documents qui ont un tableau contenant «X». Vous pouvez utiliser ET logique pour effectuer d'autres comparaisons pour des requêtes supplémentaires. (Ceci est dû au fait que Firebase ne prend actuellement pas en charge de manière native les requêtes composées pour plusieurs requêtes contenant un tableau, donc les requêtes de tri 'ET' devront être effectuées côté client)
L'utilisation de tableaux dans ce style leur permettra d'être optimisés pour les écritures simultanées, ce qui est bien! Je n'ai pas testé qu'il prend en charge les demandes par lots (les documents ne le disent pas), mais je parierais qu'il le fait car c'est une solution officielle.
collection("collectionPath").
where("searchTermsArray", "array-contains", "term").get()
Search term
on entend généralement un terme entier séparé par un espace, une ponctuation, etc. des deux côtés. Si vous google en abcde
ce moment, vous ne trouverez que des résultats pour des choses comme %20abcde.
ou ,abcde!
mais pas abcdefghijk..
. même si tout l'alphabet tapé est sûrement beaucoup plus courant sur Internet, la recherche n'est pas pour abcde * c'est pour un abcde isolé
'contains'
, qui signifie exactement ce à quoi je fais référence dans de nombreux langages de programmation. Il en va de même '%searchTerm%'
d'un point de vue SQL.
Selon les documents Firestore , Cloud Firestore ne prend pas en charge l'indexation native ni la recherche de champs de texte dans les documents. De plus, télécharger une collection entière pour rechercher des champs côté client n'est pas pratique.
Des solutions de recherche tierces comme Algolia et Elastic Search sont recommandées.
1.) \uf8ff
fonctionne de la même manière que~
2.) Vous pouvez utiliser une clause where ou des clauses start end:
ref.orderBy('title').startAt(term).endAt(term + '~');
est exactement le même que
ref.where('title', '>=', term).where('title', '<=', term + '~');
3.) Non, cela ne fonctionne pas si vous inversez startAt()
et endAt()
dans chaque combinaison, cependant, vous pouvez obtenir le même résultat en créant un deuxième champ de recherche inversé et en combinant les résultats.
Exemple: vous devez d'abord enregistrer une version inversée du champ lors de la création du champ. Quelque chose comme ça:
// 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);
}
Avec cela, vous pouvez rechercher les dernières lettres d'un champ de chaîne et les premières lettres du milieu ou groupes de lettres, mais pas aléatoires. C'est plus proche du résultat souhaité. Cependant, cela ne nous aidera pas vraiment lorsque nous voulons des lettres intermédiaires ou des mots aléatoires. N'oubliez pas non plus de sauvegarder tout en minuscules ou une copie en minuscules pour la recherche, afin que la casse ne soit pas un problème.
4.) Si vous n'avez que quelques mots, la méthode de Ken Tan fera tout ce que vous voulez, ou du moins après l'avoir légèrement modifiée. Cependant, avec seulement un paragraphe de texte, vous créerez de manière exponentielle plus de 1 Mo de données, ce qui est plus grand que la taille limite du document de Firestore (je sais, je l'ai testé).
5.) Si vous pouviez combiner array-contains (ou une forme quelconque de tableaux) avec l' \uf8ff
astuce, vous pourriez avoir une recherche viable qui n'atteindrait pas les limites. J'ai essayé toutes les combinaisons, même avec des cartes, et je n'ai pas réussi. N'importe qui le comprend, postez-le ici.
6.) Si vous devez vous éloigner de ALGOLIA et ELASTIC SEARCH et que je ne vous en veux pas du tout, vous pouvez toujours utiliser mySQL, postSQL ou neo4Js sur Google Cloud. Ils sont tous 3 faciles à configurer et ont des niveaux gratuits. Vous auriez une fonction cloud pour enregistrer les données onCreate () et une autre fonction onCall () pour rechercher les données. Simple ... ish. Pourquoi ne pas simplement passer à mySQL alors? Les données en temps réel bien sûr! Quand quelqu'un écrit DGraph avec des websocks pour des données en temps réel, comptez-moi!
Algolia et ElasticSearch ont été conçus pour être des bases de données de recherche uniquement, il n'y a donc rien d'aussi rapide ... mais vous payez pour cela. Google, pourquoi nous éloignez-vous de Google et ne suivez-vous pas MongoDB noSQL et n'autorisez pas les recherches?
MISE À JOUR - J'AI CRÉÉ UNE SOLUTION:
Réponse tardive mais pour tous ceux qui recherchent encore une réponse, disons que nous avons une collection d'utilisateurs et que dans chaque document de la collection, nous avons un champ "username", donc si vous voulez trouver un document où le nom d'utilisateur commence par "al" on peut faire quelque chose comme
FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")
Je suis sûr que Firebase sortira bientôt avec "string-contains" pour capturer n'importe quel index [i] startAt dans la chaîne ... Mais j'ai recherché les sites Web et trouvé cette solution pensée par quelqu'un d'autre a configuré vos données comme ce
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
})
requête comme celle-ci
firebase
.firestore()
.collection("clubs")
.where(
"titleAsArray",
"array-contains",
this.state.userQuery.toLowerCase()
)
Si vous ne souhaitez pas utiliser un service tiers comme Algolia, Firebase Cloud Functions est une excellente alternative. Vous pouvez créer une fonction qui peut recevoir un paramètre d'entrée, traiter les enregistrements côté serveur, puis renvoyer ceux qui correspondent à vos critères.
La réponse sélectionnée ne fonctionne que pour les recherches exactes et ne correspond pas au comportement de recherche naturel des utilisateurs (la recherche de «pomme» dans «Joe a mangé une pomme aujourd'hui» ne fonctionnerait pas).
Je pense que la réponse de Dan Fein ci-dessus devrait être mieux classée. Si les données String que vous recherchez sont courtes, vous pouvez enregistrer toutes les sous-chaînes de la chaîne dans un tableau de votre document, puis rechercher dans le tableau avec la requête array_contains de Firebase. Les documents Firebase sont limités à 1 Mio (1 048 576 octets) ( Quotas et limites Firebase ), soit environ 1 million de caractères enregistrés dans un document (je pense que 1 caractère ~ = 1 octet). Le stockage des sous-chaînes est correct tant que votre document n'est pas proche de 1 million de marques.
Exemple de recherche de noms d'utilisateurs:
Étape 1: ajoutez l'extension String suivante à votre projet. Cela vous permet de diviser facilement une chaîne en sous-chaînes. ( J'ai trouvé ça ici ).
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])
}
Étape 2: lorsque vous stockez le nom d'un utilisateur, stockez également le résultat de cette fonction sous forme de tableau dans le même document. Cela crée toutes les variations du texte d'origine et les stocke dans un tableau. Par exemple, l'entrée de texte "Apple" crée le tableau suivant: ["a", "p", "p", "l", "e", "ap", "pp", "pl", "le "," app "," ppl "," ple "," appl "," pple "," apple "], qui devrait englober tous les critères de recherche qu'un utilisateur peut entrer. Vous pouvez laisser maximumStringSize comme nul si vous voulez tous les résultats, cependant, s'il y a du texte long, je recommanderais de le plafonner avant que la taille du document ne devienne trop grande - quelque part environ 15 fonctionne bien pour moi (la plupart des gens ne recherchent pas de longues phrases de toute façon ).
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
}
Étape 3: Vous pouvez utiliser la fonction array_contains de Firebase!
[yourDatabasePath].whereField([savedSubstringArray], arrayContains: searchText).getDocuments....
Je pense en fait que la meilleure solution pour faire cela dans Firestore est de mettre toutes les sous-chaînes dans un tableau et de simplement faire une requête array_contains. Cela vous permet de faire une correspondance de sous-chaînes. Un peu exagéré de stocker toutes les sous-chaînes, mais si vos termes de recherche sont courts, c'est très très raisonnable.
Je viens d'avoir ce problème et j'ai trouvé une solution assez simple.
String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")
Le isGreaterThanOrEqualTo nous permet de filtrer le début de notre recherche et en ajoutant un "z" à la fin de isLessThanOrEqualTo, nous limitons notre recherche pour ne pas passer aux documents suivants.
Avec Firestore, vous pouvez implémenter une recherche en texte intégral, mais cela coûtera toujours plus de lectures qu'il n'en coûterait autrement, et vous devrez également entrer et indexer les données d'une manière particulière, donc dans cette approche, vous pouvez utiliser les fonctions cloud de firebase pour tokenise, puis hachez votre texte d'entrée tout en choisissant une fonction de hachage linéaire h(x)
qui satisfait à ce qui suit - if x < y < z then h(x) < h (y) < h(z)
. Pour la tokenisation, vous pouvez choisir des bibliothèques PNL légères afin de maintenir le temps de démarrage à froid de votre fonction bas, ce qui peut supprimer les mots inutiles de votre phrase. Ensuite, vous pouvez exécuter une requête avec l'opérateur inférieur et supérieur à dans Firestore. Tout en stockant vos données également, vous devrez vous assurer que vous hachez le texte avant de le stocker, et stockez le texte brut également comme si vous modifiez le texte brut, la valeur hachée changera également.
Cela a parfaitement fonctionné pour moi mais pourrait causer des problèmes de performances.
Faites ceci lors de l'interrogation de Firestore:
Future<QuerySnapshot> searchResults = collectionRef
.where('property', isGreaterThanOrEqualTo: searchQuery.toUpperCase())
.getDocuments();
Faites ceci dans votre 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);
})
};
À ce jour, il existe essentiellement 3 solutions de contournement différentes, suggérées par les experts, comme réponses à la question.
Je les ai tous essayés. J'ai pensé qu'il pourrait être utile de documenter mon expérience avec chacun d'eux.
Méthode A: Utilisation de: (dbField "> =" searchString) & (dbField "<=" searchString + "\ uf8ff")
Suggéré par @Kuba & @Ankit Prajapati
.where("dbField1", ">=", searchString)
.where("dbField1", "<=", searchString + "\uf8ff");
A.1 Les requêtes Firestore peuvent uniquement effectuer des filtres de plage (>, <,> =, <=) sur un seul champ. Les requêtes avec des filtres de plage sur plusieurs champs ne sont pas prises en charge. En utilisant cette méthode, vous ne pouvez pas avoir d'opérateur de plage dans un autre champ de la base de données, par exemple un champ de date.
A.2. Cette méthode ne fonctionne PAS pour rechercher dans plusieurs champs en même temps. Par exemple, vous ne pouvez pas vérifier si une chaîne de recherche se trouve dans l'un des fichiers (nom, notes et adresse).
Méthode-B: Utilisation d'une MAP de chaînes de recherche avec "vrai" pour chaque entrée de la carte, & utilisation de l'opérateur "==" dans les requêtes
Suggéré par @Gil Gilbert
document1 = {
'searchKeywordsMap': {
'Jam': true,
'Butter': true,
'Muhamed': true,
'Green District': true,
'Muhamed, Green District': true,
}
}
.where(`searchKeywordsMap.${searchString}`, "==", true);
B.1 De toute évidence, cette méthode nécessite un traitement supplémentaire à chaque fois que des données sont enregistrées dans la base de données et, plus important encore, nécessite un espace supplémentaire pour stocker la carte des chaînes de recherche.
B.2 Si une requête Firestore a une seule condition comme celle ci-dessus, aucun index ne doit être créé au préalable. Cette solution fonctionnerait très bien dans ce cas.
B.3 Cependant, si la requête a une autre condition, par exemple (status === "active",) il semble qu'un index soit requis pour chaque "chaîne de recherche" saisie par l'utilisateur. En d'autres termes, si un utilisateur recherche "Jam" et qu'un autre utilisateur recherche "Butter", un index doit être créé au préalable pour la chaîne "Jam", et un autre pour "Butter", etc. Sauf si vous pouvez prédire tout ce qui est possible les chaînes de recherche des utilisateurs, cela ne fonctionne PAS - dans le cas où la requête a d'autres conditions!
.where(searchKeywordsMap["Jam"], "==", true); // requires an index on searchKeywordsMap["Jam"]
.where("status", "==", "active");
** Méthode-C: Utilisation d'un ARRAY de chaînes de recherche et de l'opérateur "array-contains"
Suggéré par @Albert Renshaw et démontré par @Nick Carducci
document1 = {
'searchKeywordsArray': [
'Jam',
'Butter',
'Muhamed',
'Green District',
'Muhamed, Green District',
]
}
.where("searchKeywordsArray", "array-contains", searchString);
C.1 Semblable à la méthode B, cette méthode nécessite un traitement supplémentaire à chaque fois que des données sont enregistrées dans la base de données et, plus important encore, nécessite un espace supplémentaire pour stocker le tableau de chaînes de recherche.
C.2 Les requêtes Firestore peuvent inclure au plus une clause "array-contains" ou "array-contains-any" dans une requête composée.
Limitations générales:
Il n’existe pas de solution unique. Chaque solution de contournement a ses limites. J'espère que les informations ci-dessus peuvent vous aider pendant le processus de sélection entre ces solutions de contournement.
Pour obtenir la liste des conditions de requête Firestore, veuillez consulter la documentation https://firebase.google.com/docs/firestore/query-data/queries .
Je n'ai pas essayé https://fireblog.io/blog/post/firestore-full-text-search , qui est suggéré par @Jonathan.
Nous pouvons utiliser le back-tick pour afficher la valeur d'une chaîne. Cela devrait fonctionner:
where('name', '==', `${searchTerm}`)