Estoy buscando agregar un campo de búsqueda simple, me gustaría usar algo como
collectionRef.where('name', 'contains', 'searchTerm')
Intenté usar where('name', '==', '%searchTerm%')
, pero no devolvió nada.
Estoy buscando agregar un campo de búsqueda simple, me gustaría usar algo como
collectionRef.where('name', 'contains', 'searchTerm')
Intenté usar where('name', '==', '%searchTerm%')
, pero no devolvió nada.
Respuestas:
No hay tal operador, se admiten los son ==
, <
, <=
, >
, >=
.
Puede filtrar solo por prefijos, por ejemplo, para todo lo que comienza entre bar
y foo
puede usar
collectionRef.where('name', '>=', 'bar').where('name', '<=', 'foo')
Puede usar un servicio externo como Algolia o ElasticSearch para eso.
tennis
, pero según los operadores de consulta disponibles, no hay forma de obtener esos resultados. Combinando >=
y <=
no funciona. Por supuesto que puedo usar Algolia, pero también podría usarlo con Firebase para hacer la mayoría de las consultas y no necesito cambiar a Firestore ...
Si bien la respuesta de Kuba es cierta en lo que respecta a las restricciones, puede emular parcialmente esto con una estructura similar a un conjunto:
{
'terms': {
'reebok': true,
'mens': true,
'tennis': true,
'racket': true
}
}
Ahora puedes consultar con
collectionRef.where('terms.tennis', '==', true)
Esto funciona porque Firestore creará automáticamente un índice para cada campo. Desafortunadamente, esto no funciona directamente para consultas compuestas porque Firestore no crea índices compuestos automáticamente.
Todavía puede solucionar esto almacenando combinaciones de palabras, pero esto se pone feo rápidamente.
Probablemente aún esté mejor con una búsqueda de texto completo externa .
where
Estoy de acuerdo con la respuesta de @ Kuba, pero aún así, debe agregar un pequeño cambio para que funcione perfectamente para la búsqueda por prefijo. aquí lo que funcionó para mí
Para buscar registros que comiencen con el nombre queryText
collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff')
.
El carácter \uf8ff
utilizado en la consulta es un punto de código muy alto en el rango Unicode (es un código de Área de uso privada [PUA]). Debido a que va después de la mayoría de los caracteres regulares en Unicode, la consulta coincide con todos los valores que comienzan con queryText
.
Si bien Firebase no admite explícitamente la búsqueda de un término dentro de una cadena,
Firebase (ahora) admite lo siguiente que resolverá su caso y muchos otros:
A partir de agosto de 2018, admiten array-contains
consultas. Ver: https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html
Ahora puede establecer todos sus términos clave en una matriz como un campo y luego consultar todos los documentos que tienen una matriz que contiene 'X'. Puede utilizar AND lógico para realizar más comparaciones para consultas adicionales. (Esto se debe a que firebase actualmente no admite de forma nativa consultas compuestas para múltiples consultas que contienen matrices, por lo que las consultas de clasificación 'Y' deberán realizarse en el extremo del cliente)
El uso de matrices en este estilo permitirá que se optimicen para escrituras simultáneas, lo cual es bueno. No he probado que admita solicitudes por lotes (los documentos no lo dicen), pero apuesto a que sí, ya que es una solución oficial.
collection("collectionPath").
where("searchTermsArray", "array-contains", "term").get()
Search term
se entiende típicamente como un término completo separado por espacio, puntuación, etc. en ambos lados. Si abcde
busca en Google en este momento, solo encontrará resultados para cosas como %20abcde.
o ,abcde!
pero no abcdefghijk..
. aunque seguramente todo el alfabeto escrito es mucho más común de encontrar en Internet, la búsqueda no es para abcde * es para un abcde aislado
'contains'
, que significa exactamente a lo que me refiero en muchos lenguajes de programación. Lo mismo ocurre '%searchTerm%'
desde el punto de vista de SQL.
Según los documentos de Firestore , Cloud Firestore no admite la indexación nativa ni la búsqueda de campos de texto en los documentos. Además, descargar una colección completa para buscar campos en el lado del cliente no es práctico.
Se recomiendan soluciones de búsqueda de terceros como Algolia y Elastic Search .
1.) \uf8ff
funciona de la misma manera que~
2.) Puede utilizar una cláusula where o cláusulas start end:
ref.orderBy('title').startAt(term).endAt(term + '~');
es exactamente lo mismo que
ref.where('title', '>=', term).where('title', '<=', term + '~');
3.) No, no funciona si invierte startAt()
y endAt()
en todas las combinaciones, sin embargo, puede lograr el mismo resultado creando un segundo campo de búsqueda que se invierte y combinando los resultados.
Ejemplo: primero debe guardar una versión inversa del campo cuando se crea el campo. Algo como esto:
// 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 esto, puede buscar las últimas letras de un campo de cadena y la primera , pero no letras intermedias o grupos de letras al azar. Esto está más cerca del resultado deseado. Sin embargo, esto realmente no nos ayudará cuando queremos letras o palabras medias al azar. Además, recuerde guardar todo en minúsculas, o una copia en minúsculas para buscar, para que el uso de mayúsculas y minúsculas no sea un problema.
4.) Si solo tiene unas pocas palabras, el método de Ken Tan hará todo lo que desee, o al menos después de modificarlo ligeramente. Sin embargo, con solo un párrafo de texto, creará exponencialmente más de 1 MB de datos, que es más grande que el límite de tamaño del documento de Firestore (lo sé, lo probé).
5.) Si pudiera combinar array-contains (o alguna forma de arrays) con el \uf8ff
truco, podría tener una búsqueda viable que no alcance los límites. Probé todas las combinaciones, incluso con mapas, y no lo hice. Cualquiera que se dé cuenta de esto, publíquelo aquí.
6.) Si debes alejarte de ALGOLIA y ELASTIC SEARCH, y no te culpo en absoluto, siempre puedes usar mySQL, postSQL o neo4Js en Google Cloud. Son 3 fáciles de configurar y tienen niveles gratuitos. Tendría una función en la nube para guardar los datos onCreate () y otra función onCall () para buscar los datos. Simple ... ish. ¿Por qué no cambiar a mySQL entonces? ¡Los datos en tiempo real, por supuesto! Cuando alguien escribe DGraph con websocks para obtener datos en tiempo real, ¡cuenta conmigo!
Algolia y ElasticSearch se crearon para ser bases de datos de solo búsqueda, por lo que no hay nada tan rápido ... pero usted paga por ello. Google, ¿por qué nos alejas de Google y no sigues MongoDB noSQL y permites las búsquedas?
ACTUALIZACIÓN - CREÉ UNA SOLUCIÓN:
Respuesta tardía, pero para cualquiera que todavía esté buscando una respuesta, digamos que tenemos una colección de usuarios y en cada documento de la colección tenemos un campo de "nombre de usuario", así que si quieres encontrar un documento donde el nombre de usuario comience con "al" podemos hacer algo como
FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")
Estoy seguro de que Firebase saldrá pronto con "string-contains" para capturar cualquier índice [i] startAt en la cadena ... Pero investigué las web y encontré esta solución pensada por otra persona que configuró sus datos como esta
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
})
consulta como esta
firebase
.firestore()
.collection("clubs")
.where(
"titleAsArray",
"array-contains",
this.state.userQuery.toLowerCase()
)
Si no desea utilizar un servicio de terceros como Algolia, Firebase Cloud Functions es una excelente alternativa. Puede crear una función que pueda recibir un parámetro de entrada, procesar a través de los registros del lado del servidor y luego devolver los que coincidan con sus criterios.
De hecho, creo que la mejor solución para hacer esto dentro de Firestore es poner todas las subcadenas en una matriz y simplemente hacer una consulta array_contains. Esto le permite hacer una coincidencia de subcadenas. Es un poco exagerado almacenar todas las subcadenas, pero si los términos de búsqueda son cortos, es muy razonable.
Acabo de tener este problema y se me ocurrió una solución bastante simple.
String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")
IsGreaterThanOrEqualTo nos permite filtrar el comienzo de nuestra búsqueda y al agregar una "z" al final de isLessThanOrEqualTo limitamos nuestra búsqueda para no pasar a los siguientes documentos.
La respuesta seleccionada solo funciona para búsquedas exactas y no es un comportamiento de búsqueda natural del usuario (la búsqueda de "manzana" en "Joe comió una manzana hoy" no funcionaría).
Creo que la respuesta de Dan Fein anterior debería tener una clasificación más alta. Si los datos de cadena que está buscando son cortos, puede guardar todas las subcadenas de la cadena en una matriz en su documento y luego buscar a través de la matriz con la consulta array_contains de Firebase. Los documentos de Firebase están limitados a 1 MiB (1.048.576 bytes) ( Cuotas y límites de Firebase ), que es aproximadamente 1 millón de caracteres guardados en un documento (creo que 1 carácter ~ = 1 byte). El almacenamiento de las subcadenas está bien siempre que su documento no se acerque a la marca de 1 millón.
Ejemplo para buscar nombres de usuario:
Paso 1: agregue la siguiente extensión de cadena a su proyecto. Esto le permite dividir fácilmente una cadena en subcadenas. ( Encontré esto aquí ).
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])
}
Paso 2: cuando almacena el nombre de un usuario, también almacena el resultado de esta función como una matriz en el mismo documento. Esto crea todas las variaciones del texto original y las almacena en una matriz. Por ejemplo, la entrada de texto "Apple" crearía la siguiente matriz: ["a", "p", "p", "l", "e", "ap", "pp", "pl", "le "," app "," ppl "," ple "," appl "," pple "," apple "], que debe abarcar todos los criterios de búsqueda que un usuario pueda ingresar. Puede dejar maximumStringSize como nulo si desea todos los resultados, sin embargo, si hay texto largo, recomendaría limitarlo antes de que el tamaño del documento sea demasiado grande; alrededor de 15 funciona bien para mí (la mayoría de las personas no buscan frases largas de todos modos ).
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
}
Paso 3: ¡Puedes usar la función array_contains de Firebase!
[yourDatabasePath].whereField([savedSubstringArray], arrayContains: searchText).getDocuments....
Con Firestore puede implementar una búsqueda de texto completo, pero aún costará más lecturas de las que tendría de otra manera, y también deberá ingresar e indexar los datos de una manera particular, por lo que en este enfoque puede usar las funciones de la nube de firebase para tokenise y luego hash su texto de entrada mientras elige una función hash lineal h(x)
que satisfaga lo siguiente: if x < y < z then h(x) < h (y) < h(z)
. Para la tokenización, puede elegir algunas bibliotecas ligeras de PNL para mantener bajo el tiempo de inicio en frío de su función que puede eliminar palabras innecesarias de su oración. Luego, puede ejecutar una consulta con un operador menor que y mayor que en Firestore. Mientras también almacena sus datos, tendrá que asegurarse de aplicar hash al texto antes de almacenarlo, y almacenar el texto sin formato también, como si cambiara el texto sin formato, el valor hash también cambiará.
Esto funcionó perfectamente para mí, pero podría causar problemas de rendimiento.
Haga esto al consultar firestore:
Future<QuerySnapshot> searchResults = collectionRef
.where('property', isGreaterThanOrEqualTo: searchQuery.toUpperCase())
.getDocuments();
Haga esto en su 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);
})
};
A día de hoy, existen básicamente 3 soluciones alternativas, que fueron sugeridas por los expertos, como respuestas a la pregunta.
Los he probado todos. Pensé que podría ser útil documentar mi experiencia con cada uno de ellos.
Método-A: Usando: (dbField "> =" searchString) & (dbField "<=" searchString + "\ uf8ff")
Sugerido por @Kuba & @Ankit Prajapati
.where("dbField1", ">=", searchString)
.where("dbField1", "<=", searchString + "\uf8ff");
A.1 Las consultas de Firestore solo pueden realizar filtros de rango (>, <,> =, <=) en un solo campo. No se admiten consultas con filtros de rango en varios campos. Al usar este método, no puede tener un operador de rango en ningún otro campo de la base de datos, por ejemplo, un campo de fecha.
A.2. Este método NO funciona para buscar en varios campos al mismo tiempo. Por ejemplo, no puede verificar si una cadena de búsqueda está en alguno de los archivos (nombre, notas y dirección).
Método B: usar un MAPA de cadenas de búsqueda con "verdadero" para cada entrada en el mapa y usar el operador "==" en las consultas
Sugerido por @Gil Gilbert
document1 = {
'searchKeywordsMap': {
'Jam': true,
'Butter': true,
'Muhamed': true,
'Green District': true,
'Muhamed, Green District': true,
}
}
.where(`searchKeywordsMap.${searchString}`, "==", true);
B.1 Obviamente, este método requiere procesamiento adicional cada vez que se guardan datos en la base de datos y, lo que es más importante, requiere espacio adicional para almacenar el mapa de cadenas de búsqueda.
B.2 Si una consulta de Firestore tiene una condición única como la anterior, no es necesario crear un índice de antemano. Esta solución funcionaría bien en este caso.
B.3 Sin embargo, si la consulta tiene otra condición, por ejemplo (estado === "activo"), parece que se requiere un índice para cada "cadena de búsqueda" que ingresa el usuario. En otras palabras, si un usuario busca "Mermelada" y otro usuario busca "Mantequilla", se debe crear un índice de antemano para la cadena "Mermelada" y otro para "Mantequilla", etc. A menos que pueda predecir todo lo posible cadenas de búsqueda de los usuarios, esto NO funciona, ¡en caso de que la consulta tenga otras condiciones!
.where(searchKeywordsMap["Jam"], "==", true); // requires an index on searchKeywordsMap["Jam"]
.where("status", "==", "active");
** Método-C: usando un ARRAY de cadenas de búsqueda y el operador "array-contains"
Sugerido por @Albert Renshaw y demostrado por @Nick Carducci
document1 = {
'searchKeywordsArray': [
'Jam',
'Butter',
'Muhamed',
'Green District',
'Muhamed, Green District',
]
}
.where("searchKeywordsArray", "array-contains", searchString);
C.1 Similar al Método B, este método requiere un procesamiento adicional cada vez que se guardan datos en la base de datos y, lo que es más importante, requiere espacio adicional para almacenar la matriz de cadenas de búsqueda.
C.2 Las consultas de Firestore pueden incluir como máximo una cláusula "array-contains" o "array-contains-any" en una consulta compuesta.
Limitaciones generales:
No existe una solución única que se adapte a todos. Cada solución alternativa tiene sus limitaciones. Espero que la información anterior pueda ayudarlo durante el proceso de selección entre estas soluciones.
Para obtener una lista de las condiciones de consulta de Firestore, consulte la documentación https://firebase.google.com/docs/firestore/query-data/queries .
No he probado https://fireblog.io/blog/post/firestore-full-text-search , que es sugerido por @Jonathan.
Podemos usar la marca inversa para imprimir el valor de una cadena. Esto debería funcionar:
where('name', '==', `${searchTerm}`)