Como funciona a busca textual do MongoDB

MongoDB

MongoDB

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.

4 Respostas para Como funciona a busca textual do MongoDB

  1. Mario 05/03/2013 em 9:26 pm #

    Muito obrigado! E um gram essemplo.

  2. Luiz Felipe Mendes 06/03/2013 em 11:13 am #

    Olá Christiano,

    Interessante o post, mostrando o básico da busca textual de MongoDB. Minha maior dúvida era com relação a utilização desse índice com outros campos, por exemplo, gostaria de filtrar antes de fazer a busca textual por um projeto, isto é, gostaria que meu índice fosse algo como

    { “project”:1 , “campoDeTexto” : “text”}

    Talvez até adicionar outros campos após o field de texto.

    Pelo que li até agora isso não me parece possível ainda, não é?

    Meu caso é bem “simples” em termos de busca textual, queria filtrar dentro de um projeto todos documentos que contém uma palavra ou sentença.

    db.Doc.find({“project”: someID, “campoDeTexto”: /alguma sentenca/ }).limit().sort()

    Atualmente eu consigo fazer isso, mas é MUITO demorado já que o índice em “campoDeTexto” não seria utilizado, apenas com “startsWith”.

    Abraços

    • Christiano Anderson 15/03/2013 em 6:46 pm #

      Oi Luiz,

      Vou fazer uns testes, não cheguei a usar o limit com sort.

      Talvez, dê uma olhada no aggregation, será que não vale?

      []s

  3. Edson 29/01/2014 em 2:53 pm #

    Prezados senhores, tenho uma solução de busca nominal e textual, que utiliza a fonetica da lingua portuguesa e da linha espanhola, utilizamos também o que chamamos de dicionarios, onde o usuário pode criar conforme exemplo: vc=voce, tbm=também, casa=moradia=lar, tatianE=Tati .
    Para maiores informações sobre a ferramenta me mande um e-mail: edsonfrosa@hotmail.com

Deixe uma resposta

Contribua com Bitcoins :-) 19SNYXo1d4Wy4isCXPZr9HtV8SR2h1Ln6v