Estou tentando adicionar um campo de pesquisa simples, gostaria de usar algo como
collectionRef.where('name', 'contains', 'searchTerm')
Tentei usar where('name', '==', '%searchTerm%')
, mas não retornou nada.
Estou tentando adicionar um campo de pesquisa simples, gostaria de usar algo como
collectionRef.where('name', 'contains', 'searchTerm')
Tentei usar where('name', '==', '%searchTerm%')
, mas não retornou nada.
Respostas:
Não há tal operador, aqueles permitidos são ==
, <
, <=
, >
, >=
.
Você pode filtrar por prefixos apenas, por exemplo, para tudo que começa entre bar
e foo
você pode usar
collectionRef.where('name', '>=', 'bar').where('name', '<=', 'foo')
Você pode usar um serviço externo como Algolia ou ElasticSearch para isso.
tennis
, mas com base nos operadores de consulta disponíveis, não há como obter esses resultados. Combinando >=
e <=
não funciona. Claro que posso usar o Algolia, mas também poderia usá-lo com o Firebase para fazer a maioria das consultas e não precisar mudar para o Firestore ...
Embora a resposta de Kuba seja verdadeira no que diz respeito às restrições, você pode emular isso parcialmente com uma estrutura semelhante a um conjunto:
{
'terms': {
'reebok': true,
'mens': true,
'tennis': true,
'racket': true
}
}
Agora você pode consultar com
collectionRef.where('terms.tennis', '==', true)
Isso funciona porque o Firestore criará automaticamente um índice para cada campo. Infelizmente, isso não funciona diretamente para consultas compostas porque o Firestore não cria índices compostos automaticamente.
Você ainda pode contornar isso armazenando combinações de palavras, mas isso fica feio rápido.
Provavelmente, você ainda está melhor com uma pesquisa de texto completa externa .
where
Eu concordo com a resposta de @Kuba, mas ainda assim, é necessário adicionar uma pequena alteração para funcionar perfeitamente para pesquisa por prefixo. aqui o que funcionou para mim
Para pesquisar registros começando com o nome queryText
collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff')
.
O caractere \uf8ff
usado na consulta é um ponto de código muito alto no intervalo Unicode (é um código de área de uso privado [PUA]). Como ocorre após a maioria dos caracteres regulares em Unicode, a consulta corresponde a todos os valores que começam com queryText
.
Embora o Firebase não suporte explicitamente a pesquisa de um termo em uma string,
O Firebase (agora) oferece suporte ao seguinte, o que resolverá o seu caso e muitos outros:
Desde agosto de 2018, eles suportam array-contains
consultas. Veja: https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html
Agora você pode definir todos os seus termos-chave em uma matriz como um campo e, em seguida, consultar todos os documentos que possuem uma matriz que contém 'X'. Você pode usar o AND lógico para fazer comparações adicionais para consultas adicionais. (Isso ocorre porque o firebase atualmente não oferece suporte nativo a consultas compostas para várias consultas array-contains, portanto, as consultas de classificação 'AND' terão que ser feitas no lado do cliente)
Usar arrays neste estilo permitirá que eles sejam otimizados para gravações simultâneas, o que é bom! Não testei se ele oferece suporte a solicitações em lote (os documentos não dizem), mas aposto que sim, pois é uma solução oficial.
collection("collectionPath").
where("searchTermsArray", "array-contains", "term").get()
Search term
normalmente é entendido como um termo inteiro separado por espaço, pontuação, etc. em ambos os lados. Se você pesquisar no Google abcde
agora, só encontrará resultados para coisas como %20abcde.
ou ,abcde!
mas não abcdefghijk..
. embora certamente todo o alfabeto digitado seja muito mais comum de ser encontrado na internet, a busca não é por abcde * é por um abcde isolado
'contains'
, que significa exatamente o que estou me referindo em muitas linguagens de programação. O mesmo se '%searchTerm%'
aplica do ponto de vista SQL.
De acordo com os documentos do Firestore , o Cloud Firestore não oferece suporte para indexação nativa ou pesquisa de campos de texto em documentos. Além disso, baixar uma coleção inteira para pesquisar campos do lado do cliente não é prático.
Soluções de pesquisa de terceiros como Algolia e Elastic Search são recomendadas.
1.) \uf8ff
funciona da mesma maneira que~
2.) Você pode usar uma cláusula where ou cláusulas start end:
ref.orderBy('title').startAt(term).endAt(term + '~');
é exatamente o mesmo que
ref.where('title', '>=', term).where('title', '<=', term + '~');
3.) Não, não funciona se você inverter startAt()
e endAt()
em todas as combinações, no entanto, você pode obter o mesmo resultado criando um segundo campo de pesquisa que é invertido e combinando os resultados.
Exemplo: primeiro você deve salvar uma versão reversa do campo quando o campo é criado. Algo assim:
// 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);
}
Com isso, você pode pesquisar as últimas letras de um campo de string e as primeiras , não apenas letras do meio aleatórias ou grupos de letras. Isso está mais próximo do resultado desejado. No entanto, isso não vai realmente nos ajudar quando queremos letras do meio ou palavras aleatórias. Além disso, lembre-se de salvar tudo em letras minúsculas ou uma cópia em minúsculas para pesquisa, para que a capitalização não seja um problema.
4.) Se você tiver apenas algumas palavras, o Método de Ken Tan fará tudo o que você quiser, ou pelo menos depois de modificá-lo ligeiramente. No entanto, com apenas um parágrafo de texto, você criará exponencialmente mais de 1 MB de dados, que é maior do que o limite de tamanho do documento do firestore (eu sei, eu testei).
5.) Se você pudesse combinar o array-contém (ou alguma forma de arrays) com o \uf8ff
truque, você poderia ter uma busca viável que não alcança os limites. Tentei todas as combinações, mesmo com mapas, e não consegui. Se alguém descobrir isso, poste aqui.
6.) Se você precisa se afastar da ALGOLIA e do ELASTIC SEARCH, e eu não culpo você de forma alguma, você sempre pode usar mySQL, postSQL ou neo4Js no Google Cloud. São três fáceis de configurar e têm níveis gratuitos. Você teria uma função de nuvem para salvar os dados onCreate () e outra função onCall () para pesquisar os dados. Simples ... ish. Por que não mudar para o mySQL então? Os dados em tempo real, é claro! Quando alguém escreve DGraph com websocks para dados em tempo real, conte comigo!
Algolia e ElasticSearch foram construídos para serem bancos de dados somente para pesquisa, então não há nada tão rápido ... mas você paga por isso. Google, por que você nos afasta do Google e não segue o MongoDB noSQL e permite pesquisas?
ATUALIZAÇÃO - CRIEI UMA SOLUÇÃO:
Resposta tardia, mas para quem ainda está procurando uma resposta, digamos que temos uma coleção de usuários e em cada documento da coleção temos um campo "nome de usuário", portanto, se quiser encontrar um documento onde o nome de usuário comece com "al" podemos fazer algo como
FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")
Tenho certeza de que o Firebase sairá com "string-contém" em breve para capturar qualquer índice [i] startAt na string ... Mas eu pesquisei as redes e encontrei esta solução pensada por outra pessoa para configurar seus dados como isto
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()
)
Se você não quiser usar um serviço de terceiros como o Algolia, o Firebase Cloud Functions é uma ótima alternativa. Você pode criar uma função que pode receber um parâmetro de entrada, processar os registros do lado do servidor e retornar aqueles que correspondem aos seus critérios.
Na verdade, acho que a melhor solução para fazer isso no Firestore é colocar todas as substrings em uma matriz e apenas fazer uma consulta array_contains. Isso permite que você faça correspondência de substring. Um pouco exagerado armazenar todas as substrings, mas se seus termos de pesquisa forem curtos, é muito razoável.
Acabei de ter esse problema e encontrei uma solução bem simples.
String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")
O isGreaterThanOrEqualTo nos permite filtrar o início de nossa pesquisa e, adicionando um "z" ao final de isLessThanOrEqualTo, limitamos nossa pesquisa para não passar para os próximos documentos.
A resposta selecionada só funciona para pesquisas exatas e não é um comportamento de pesquisa natural do usuário (pesquisar "maçã" em "João comeu uma maçã hoje" não funcionaria).
Acho que a resposta de Dan Fein acima deveria ser classificada em posição superior. Se os dados da String que você está pesquisando forem curtos, você pode salvar todas as substrings da string em uma matriz em seu Documento e, em seguida, pesquisar na matriz com a consulta array_contains do Firebase. Os documentos do Firebase são limitados a 1 MiB (1.048.576 bytes) ( cotas e limites do Firebase ), o que corresponde a cerca de 1 milhão de caracteres salvos em um documento (acho que 1 caractere ~ = 1 byte). Não há problema em armazenar substrings, desde que o documento não esteja perto da marca de 1 milhão.
Exemplo para pesquisar nomes de usuário:
Etapa 1: adicione a seguinte extensão String ao seu projeto. Isso permite que você divida facilmente uma string em substrings. ( Eu encontrei isso aqui ).
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])
}
Etapa 2: Ao armazenar o nome de um usuário, armazene também o resultado desta função como uma matriz no mesmo Documento. Isso cria todas as variações do texto original e as armazena em uma matriz. Por exemplo, a entrada de texto "Apple" criaria a seguinte matriz: ["a", "p", "p", "l", "e", "ap", "pp", "pl", "le "," app "," ppl "," ple "," appl "," pple "," apple "], que deve abranger todos os critérios de pesquisa que um usuário pode inserir. Você pode deixar maximumStringSize como nulo se quiser todos os resultados, no entanto, se houver texto longo, eu recomendaria limitá-lo antes que o tamanho do documento fique muito grande - algo em torno de 15 funciona bem para mim (a maioria das pessoas não pesquisa frases longas de qualquer maneira )
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
}
Etapa 3: você pode usar a função array_contains do Firebase!
[yourDatabasePath].whereField([savedSubstringArray], arrayContains: searchText).getDocuments....
Com o Firestore, você pode implementar uma pesquisa de texto completo, mas ainda custará mais leituras do que teria de outra forma, e você também precisará inserir e indexar os dados de uma maneira particular. Portanto, nesta abordagem, você pode usar as funções de nuvem do Firebase para tokenize e, em seguida, hash seu texto de entrada ao escolher uma função hash linear h(x)
que satisfaça o seguinte - if x < y < z then h(x) < h (y) < h(z)
. Para tokenização, você pode escolher algumas Bibliotecas de PNL leves para manter o tempo de inicialização a frio de sua função baixo, o que pode retirar palavras desnecessárias de sua frase. Em seguida, você pode executar uma consulta com menor que e maior que operador no Firestore. Ao armazenar seus dados também, você terá que certificar-se de fazer o hash do texto antes de armazená-lo, e armazenar o texto simples também como se você alterasse o texto simples, o valor hash também mudaria.
Isso funcionou perfeitamente para mim, mas pode causar problemas de desempenho.
Faça isso ao consultar o firestore:
Future<QuerySnapshot> searchResults = collectionRef
.where('property', isGreaterThanOrEqualTo: searchQuery.toUpperCase())
.getDocuments();
Faça isso no seu 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 partir de hoje, existem basicamente 3 soluções alternativas diferentes, que foram sugeridas pelos especialistas, como respostas à pergunta.
Eu tentei todos eles. Achei que seria útil documentar minha experiência com cada um deles.
Método-A: usando: (dbField "> =" searchString) & (dbField "<=" searchString + "\ uf8ff")
Sugerido por @Kuba e @Ankit Prajapati
.where("dbField1", ">=", searchString)
.where("dbField1", "<=", searchString + "\uf8ff");
A.1 As consultas do Firestore só podem realizar filtros de intervalo (>, <,> =, <=) em um único campo. Não há suporte para consultas com filtros de intervalo em vários campos. Usando este método, você não pode ter um operador de intervalo em qualquer outro campo no banco de dados, por exemplo, um campo de data.
A.2. Este método NÃO funciona para pesquisar em vários campos ao mesmo tempo. Por exemplo, você não pode verificar se uma string de pesquisa está em algum dos campos (nome, notas e endereço).
Método B: usando um MAP de strings de pesquisa com "true" para cada entrada no mapa e usando o operador "==" nas 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, esse método requer processamento extra sempre que os dados são salvos no banco de dados e, mais importante, requer espaço extra para armazenar o mapa de strings de pesquisa.
B.2 Se uma consulta do Firestore tem uma única condição como a acima, nenhum índice precisa ser criado de antemão. Esta solução funcionaria muito bem neste caso.
B.3 No entanto, se a consulta tiver outra condição, por exemplo (status === "ativo"), parece que um índice é necessário para cada "string de pesquisa" que o usuário inserir. Em outras palavras, se um usuário pesquisar por "Jam" e outro usuário pesquisar por "Butter", um índice deve ser criado com antecedência para a string "Jam" e outro para "Butter", etc. A menos que você possa prever todas as possibilidades strings de pesquisa dos usuários, isso NÃO funciona - caso a consulta tenha outras condições!
.where(searchKeywordsMap["Jam"], "==", true); // requires an index on searchKeywordsMap["Jam"]
.where("status", "==", "active");
** Método C: usando um ARRAY de strings de pesquisa e o operador "array-contém"
Sugerido por @Albert Renshaw e demonstrado por @Nick Carducci
document1 = {
'searchKeywordsArray': [
'Jam',
'Butter',
'Muhamed',
'Green District',
'Muhamed, Green District',
]
}
.where("searchKeywordsArray", "array-contains", searchString);
C.1 Semelhante ao Método-B, este método requer processamento extra toda vez que os dados são salvos no banco de dados e, mais importante, requer espaço extra para armazenar a matriz de strings de pesquisa.
C.2 As consultas do Firestore podem incluir no máximo uma cláusula "array-contém" ou "array-contém-qualquer" em uma consulta composta.
Limitações gerais:
Não existe uma solução que sirva para todos. Cada solução alternativa tem suas limitações. Espero que as informações acima possam ajudá-lo durante o processo de seleção entre essas soluções alternativas.
Para obter uma lista das condições de consulta do Firestore, verifique a documentação https://firebase.google.com/docs/firestore/query-data/queries .
Não tentei https://fireblog.io/blog/post/firestore-full-text-search , que é sugerido por @Jonathan.
Podemos usar o back-tick para imprimir o valor de uma string. Isso deve funcionar:
where('name', '==', `${searchTerm}`)