A versão 2.4.0 do MongoDB vai ser lançada em breve e terá suporte a busca textual (Full Text Search – FTS) nativa! Esse recurso estava sendo solicitado desde as primeiras versões do MongoDB, consta no ticket SERVER-380 do JIRA, aberto em 2009.
Esse recurso permite realizar busca textual simples e mais complexas, incluindo uso de stemming, stopwords, negação, busca por frases e suporte a multi idiomas, incluindo o português. Atenção, esse recurso ainda está em desenvolvimento, não use em produção!
Eu fiz um teste, utilizando a versão 2.4.0-rc1 que foi lançada dia 25/02/2013 e pode ser encontrada na página de download do MongoDB.
Como habilitar a busca textual
Se você estiver usando a versão 2.4.0-rc ou superior, poderá habilitar o suporte a busca textual com o seguinte comando:
$ ./mongod --setParameter textSearchEnabled=true
Com o servidor sendo executado com o comando acima, os exemplos a seguir foram executados dentro do shell do próprio MongoDB.
O comando abaixo cria o índice no banco para a língua portuguesa:
> db.teste.ensureIndex( {txt: "text"}, {default_language: "portuguese"} )
Para verificar se o índice foi criado com sucesso, basta rodar o comando:
> db.teste.getIndices()
A saída deverá ser algo do tipo:
> db.teste.getIndices()
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"ns" : "test.teste",
"name" : "_id_"
},
{
"v" : 1,
"key" : {
"_fts" : "text",
"_ftsx" : 1
},
"ns" : "test.teste",
"name" : "txt_text",
"default_language" : "portuguese",
"weights" : {
"txt" : 1
},
"language_override" : "language",
"textIndexVersion" : 1
}
]
Repare que na linha 19, o parâmetro “default_language” confirma que o índice foi criado respeitando os padrões do idioma português.
Agora é hora de indexar alguma coisa:
> db.teste.insert( {txt: "O rato roeu a roupa do rei de Roma"} )
Abaixo, um exemplo da busca, a palavra pesquisada é apenas “roma“:
> db.teste.runCommand( "text", { search : "roma"})
{
"queryDebugString" : "rom||||||",
"language" : "portuguese",
"results" : [
{
"score" : 0.6,
"obj" : {
"_id" : ObjectId("512e28809d5aac13c2980cc9"),
"txt" : "O rato roeu a roupa do rei de Roma"
}
}
],
"stats" : {
"nscanned" : 1,
"nscannedObjects" : 0,
"n" : 1,
"nfound" : 1,
"timeMicros" : 95
},
"ok" : 1
}
Repare que o registro com o termo “roma” foi encontrado, junto com seu score.
Podemos fazer uma brincadeira para testar o stemming, vou realizar uma busca com a frase “o rato roeu a roupa da rainha de roma”. Troquei o termo “rei” por “rainha”, vejam:
> db.teste.runCommand( "text", { search : "o rato roeu a roupa da rainha de roma"})
{
"queryDebugString" : "rainh|rat|roeu|rom|roup||||||",
"language" : "portuguese",
"results" : [
{
"score" : 5.4,
"obj" : {
"_id" : ObjectId("512e28809d5aac13c2980cc9"),
"txt" : "O rato roeu a roupa do rei de Roma"
}
}
],
"stats" : {
"nscanned" : 4,
"nscannedObjects" : 0,
"n" : 1,
"nfound" : 1,
"timeMicros" : 221
},
"ok" : 1
}
O score dessa busca ficou mais alto que o da primeira. Quanto mais baixo o score, maior é a precisão da busca. Mesmo assim a busca retornou um resultado válido, encontrando o registro que contém esse termo.
Vamos agora adicionar mais algumas frases:
> db.teste.insert( {txt: "Batatinha quando nasce, espalha rama pelo chão"} )
> db.teste.insert( {txt: "Escolhe um trabalho de que gostes, e não terás que trabalhar nem um dia na tua vida."} )
> db.teste.insert( {txt: "Enquanto houver vontade de lutar, haverá esperança de vencer"} )
> db.teste.insert( {txt: "A vida só pode ser compreendida, olhando-se para trás; mas só pode ser vivida, olhando-se para frente"} )
Indexei algumas frases aleatórias encontradas na internet, sendo que duas frases possuem a palavra “vida”.
Se fizermos uma busca com o termo “vida”, teremos o seguinte resultado:
> db.teste.runCommand( "text", { search : "vida"})
{
"queryDebugString" : "vid||||||",
"language" : "portuguese",
"results" : [
{
"score" : 0.5625,
"obj" : {
"_id" : ObjectId("512e2ad69d5aac13c2980ccb"),
"txt" : "Escolhe um trabalho de que gostes, e não terás que trabalhar nem um dia na tua vida."
}
},
{
"score" : 0.5454545454545454,
"obj" : {
"_id" : ObjectId("512e2b6c9d5aac13c2980ccd"),
"txt" : "A vida só pode ser compreendida, olhando-se para trás; mas só pode ser vivida, olhando-se para frente"
}
}
],
"stats" : {
"nscanned" : 2,
"nscannedObjects" : 0,
"n" : 2,
"nfound" : 2,
"timeMicros" : 113
},
"ok" : 1
}
Agora, vejam o stemming em ação, vou fazer uma busca com o termo “vencendo”. Repare que essa palavra não está indexada, mas temos um registro com a palavra “vencer”, que é da mesma raiz da busca em questão.
> db.teste.runCommand( "text", { search : "vencendo"})
{
"queryDebugString" : "venc||||||",
"language" : "portuguese",
"results" : [
{
"score" : 0.5714285714285714,
"obj" : {
"_id" : ObjectId("512e2b2d9d5aac13c2980ccc"),
"txt" : "Enquanto houver vontade de lutar, haverá esperança de vencer"
}
}
],
"stats" : {
"nscanned" : 1,
"nscannedObjects" : 0,
"n" : 1,
"nfound" : 1,
"timeMicros" : 98
},
"ok" : 1
}
Outro exemplo, com o verbo “nascer”, que também não existe, mas uma palavra com a mesma raiz foi indexada: “nasce”:
> db.teste.runCommand( "text", { search : "nascer"})
{
"queryDebugString" : "nasc||||||",
"language" : "portuguese",
"results" : [
{
"score" : 0.5714285714285714,
"obj" : {
"_id" : ObjectId("512e2a929d5aac13c2980cca"),
"txt" : "Batatinha quando nasce, espalha rama pelo chão"
}
}
],
"stats" : {
"nscanned" : 1,
"nscannedObjects" : 0,
"n" : 1,
"nfound" : 1,
"timeMicros" : 96
},
"ok" : 1
}
Para finalizar, vamos fazer uma busca com negação. Nesse caso quero todos os registros que contenham a palavra “vida” mas que não tenha a palavra “escolhe”, fazemos assim:
> db.teste.runCommand( "text", { search : "vida -escolhe"})
{
"queryDebugString" : "vid||escolh||||",
"language" : "portuguese",
"results" : [
{
"score" : 0.5454545454545454,
"obj" : {
"_id" : ObjectId("512e2b6c9d5aac13c2980ccd"),
"txt" : "A vida só pode ser compreendida, olhando-se para trás; mas só pode ser vivida, olhando-se para frente"
}
}
],
"stats" : {
"nscanned" : 2,
"nscannedObjects" : 0,
"n" : 1,
"nfound" : 1,
"timeMicros" : 192
},
"ok" : 1
}
O registro que tinha o termo “escolhe” foi ignorado do resultado, como esperado.
São exemplos simples, mas mostram que a busca textual do MongoDB possui um grande potencial.
Estou fazendo alguns testes de realizar buscas dentro de uma aplicação desenvolvida em Python (utilizando o PyMongo). Assim que tiver material suficiente, vou escrever outro post.
