En esta entrada del blog, echaremos un vistazo a la API de vista previa para la búsqueda de texto completo en Couchbase 4.5. Tenga en cuenta que esta API, publicada en el último SDK de Java (2.2.4
), sigue siendo @Experimental
.
Cubriremos:
- ¿Búsqueda de texto completo en Couchbase?
- La API de Java
- Varios tipos de consultas
- Obtener explicaciones de los aciertos
- Conclusión
Esta API experimental se puede utilizar con Couchbase Server 4.5 Developer Preview, siempre que se utilice la extensión 2.2.4
Java SDK client, que puedes obtener a través de Maven. Añada la siguiente dependencia a su pom.xml
:
1 2 3 4 5 |
com.couchbase.cliente java-cliente 2.2.4 |
¿Búsqueda de texto completo en Couchbase?
Sí. El próximo 4.5
(nombre en clave Watson) incluirá un indexador de texto completo (FTS, también conocido como CBFT) basado en el programa de código abierto Bleve proyecto. Bleve trata sobre la búsqueda de texto completo y la indexación en Go (un saludo a nuestro propio Marty Schoch por iniciar este proyecto).
La idea es aprovechar Bleve para proporcionar una búsqueda de texto completo off-the-shelf en Couchbase Server, sin tener que utilizar conectores a software externo (que se ejecuta en su propio clúster). Si esa solución "off-the-shelf" no satisface sus necesidades hasta el final, por supuesto, todavía puede utilizar estos conectorespero para las necesidades más sencillas se puede optar por una única solución.
FTS ofrece una gran cantidad de capacidades que son proporcionadas por Bleve: Analizadores de texto, tokenizadores y filtros de tokens de posprocesamiento que van más allá del alcance de este post, así como los numerosos tipos de consultas que puede ejecutar en los índices resultantes. Veamos cuáles son esos tipos y cómo puedes esperar utilizarlos en el contexto del SDK de Java.
En el resto de esta entrada de blog, utilizaremos 3 índices que podrá crear a través de la consola de administración web en la próxima versión 4.5 Developer Preview:
Esta es la lista de índices de la interfaz de usuario:
Tenemos:
- a
beerIndex
que indexa todo el contenido de cada documento del
cubo.cerveza-muestra
- a
travelIndex
que indexa todo el contenido de cada documento del
cubo.viaje-muestra
- un índice de alias,
commonIndex
que es una unión de los dos índices anteriores.
La API de Java
El punto de entrada de la función de búsqueda de texto completo en el SDK de Java se encuentra en la carpeta Cubo
utilizando el query(SearchQuery ftq)
método. Esto es coherente con los métodos de consulta existentes ya presentes en la API para ejecutar un ViewQuery
o un N1qlQuery
.
La API para la búsqueda de texto completo sigue el constructor patrón. Identifique el tipo de consulta que desea y utilice el constructor correspondiente para construirla, obtenga la función BúsquedaQuery
utilizando construir()
y ejecutarlo utilizando bucket.query(searchQuery)
.
Tomemos un ejemplo (muy sencillo) y veamos cómo se puede consumir:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//utilizaremos ese Cluster y Bucket para el resto de los ejemplos Grupo grupo = CouchbaseCluster.crear("127.0.0.1"); Cubo cubo = grupo.openBucket("muestra de cerveza"); //utilizamos una forma sencilla de consulta: BúsquedaQuery ftq = MatchQuery.en("beerIndex").match("nacional").límite(3).construya(); //disparamos la consulta y miramos los resultados SearchQueryResult resultado = cubo.consulta(ftq); Sistema.fuera.println("totalHits " + resultado.totalHits()); para (BúsquedaQueryRow fila : resultado) { Sistema.fuera.println(fila); } |
Si examinamos cada sección por separado, esto es lo que ocurrió:
- Creamos un sencillo
MatchQuery
en un solo plazo. - Se ejecuta en la muestra de cerveza (
.on(beerIndex
), busca apariciones textuales de la palabra "nacional" (.query("nacional")
) o términos cercanos. - Se realiza una configuración adicional para limitar el número de resultados a 3 (
límite(3)
) y la consulta propiamente dicha se crea en este punto (.construir()
). - La consulta se ejecuta (
bucket.query(ftq)
) y devuelve unSearchQueryResult
. - Emitimos el resultado de
totalHits()
y las filas individuales (también accesibles como lista a través deaciertos()
).
Ejecutando ese código sale:
1 2 3 4 |
totalHits: 31 BúsquedaQueryHit{id=dc_brau, puntuación=0.09068310490562362, fragmentos={}} BúsquedaQueryHit{id=brouwerij_nacional_balashi, puntuación=0.12085760187148556, fragmentos={}} BúsquedaQueryHit{id=cervecera_nacional, puntuación=0.09863195902067363, fragmentos={}} |
Vemos que el total de visitas nos da el número real de visitas antes de que se aplicara el límite. La dirección aciertos()
devuelve 3 BúsquedaQueryRow
objetos, según lo solicitado.
Cada acierto contiene la clave del documento asociado en Couchbase (id()
), así como más información sobre la coincidencia, por ejemplo, una puntuación para la coincidencia (puntuación()
)... Si lo desea, puede recuperar el documento asociado utilizando bucket.get(fila.id())
:
1 2 3 4 5 6 |
resultado = cubo.consulta(ftq); Sistema.fuera.println("totalHits " + resultado.totalHits()); para (BúsquedaQueryRow fila : resultado) { Sistema.fuera.println(fila); Sistema.fuera.println(cubo.consiga(fila.id()).contenido()); } |
Esto nos da, para el primer golpe:
1 2 3 |
BúsquedaQueryHit{id=dc_brau, puntuación=0.09068310490562362, fragmentos={}} {"país":"Estados Unidos","sitio web":"http://www.dcbrau.com/","código":"20018","dirección":["3178-B Bladensburg Rd. NE"],"ciudad":"Washington","teléfono":"","nombre":"DC Brau", "descripción":"La primera cervecería que abre en la capital del país desde la Ley Seca".,"estado":"DC","tipo":"cervecería","actualizado":"2011-08-08 19:02:40"} |
Si observamos detenidamente el JSON del documento, nos daremos cuenta de dónde probablemente coincidía el documento. En el campo "descripción
" del documento, aparece esta frase:
La primera cervecería que abrió sus puertas en nacióndesde la Prohibición.
Observe también que la consulta de texto buscó la palabra solicitada y las palabras derivadas que tienen la misma raíz. En realidad, aplicó una imprecisión de 2 (véase la sección siguiente).
Este patrón también puede aplicarse a otros tipos de consulta, así que veamos algunos más para ver qué tipo de búsqueda puede realizarse.
Varios tipos de consultas
Consulta difusa
La consulta difusa puede realizarse con la función MatchQuery
especificando un Distancia Levenshtein como máximo imprecisión()
permitir en el plazo:
1 2 3 4 5 6 7 8 9 10 11 |
resultado = cubo.consulta(MatchQuery.en("beerIndex") .match("sammar") .campo("nombre") .desenfoque(2) //por defecto .construya()); Sistema.fuera.println("Consulta de coincidencia nFuzzy"); Sistema.fuera.println(" totalHits (fuzziness = 2): " + resultado.totalHits()); para (BúsquedaQueryRow fila : resultado) { Sistema.fuera.println(cubo.consiga(fila.id()).contenido().consiga("nombre")); } |
A un nivel de 2Esto coincide con palabras como "martillo", "mamá" o "verano":
1 2 3 4 5 |
Difuso Partido Consulta totalHits (desenfoque = 2): 45 Mamma Mia! Pizza Cerveza Redhook Largo Martillo IPA Verano Trigo |
A un nivel de 1no se encuentra ninguna coincidencia:
1 2 |
Difuso Partido Consulta totalHits (desenfoque = 1): 0 |
También se ofrece un tipo de consulta dedicada a la imprecisión y que no aplica ningún analizador en el FuzzyQuery
.
Términos múltiples: MatchPhrase
Como hemos visto, MatchQuery
es una consulta basada en términos que permite especificar opcionalmente la imprecisión y también aplica al término buscado el mismo filtro que se haya podido aplicar al campo (por ejemplo, stemming, etc...):
1 2 3 4 |
MatchQuery.en("beerIndex") .match("sesonal") .desenfoque(2) .campo("descripción").construya(); |
Puede buscar varios términos en una sola consulta utilizando un atributo Frase de partido
consulta. Se analizan los términos y se puede activar opcionalmente la imprecisión:
1 |
MatchPhraseQuery.en("beerIndex").matchPhrase("estacional de verano").campo("descripción"); |
Consulta Regexp
A RegexpQuery
no sólo hace coincidencias literales, sino que permite hacer coincidencias utilizando una expresión regular. Tome este ejemplo:
1 2 3 4 5 6 7 8 9 10 |
resultado = cubo.consulta(RegexpQuery.en("beerIndex") .regexp("[tp]ale") .campo("nombre") .construya()); Sistema.fuera.println("Consulta nRegexp"); Sistema.fuera.println("totalHits " + resultado.totalHits()); para (BúsquedaQueryRow fila : resultado) { Sistema.fuera.println(cubo.consiga(fila.id()).contenido().consiga("nombre")); } |
Observe que esta consulta se dirige a un campo concreto del archivo json (campo("nombre")
). Queremos todos los nombres que contengan "cuento" o "pálido". He aquí algunos nombres que coinciden con esta búsqueda:
1 2 3 4 5 |
Regexp Consulta totalHits: 408 Alto Cuento Pálido Ale Bard's Cuento Cerveza Empresa Pálido Ale |
Prefijo Consulta
A PrefixQuery
busca apariciones de palabras que empiecen por la cadena dada:
1 2 3 4 5 6 7 8 9 10 |
resultado = cubo.consulta(PrefixQuery.en("beerIndex") .prefijo("weiss") .campo("nombre") .construya()); Sistema.fuera.println("Consulta nPrefix"); Sistema.fuera.println("totalHits " + resultado.totalHits()); para (BúsquedaQueryRow fila : resultado) { Sistema.fuera.println(cubo.consiga(fila.id()).contenido().consiga("nombre")); } |
Una vez más, sólo miramos dentro del nombre
campo, esta vez para las palabras que empiezan por "weiss":
1 2 3 4 5 6 |
Prefijo Consulta totalHits: 74 Bávaro-Weissbier Hefeweisse / Weisser Hirsch Münchner Kindl Weissbier / Münchner Weisse Franziskaner Hefe-Weissbier Infierno / Franziskaner Club-Weiss Weissenheimer Trigo |
Consultas por rango y fecha
FTS
también funciona bien con datos no textuales. Por ejemplo, el NumericRangeQuery
permite buscar valores numéricos dentro de un intervalo proporcionado:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
resultado = cubo.consulta(NumericRangeQuery.en("beerIndex") .min(3) .max(4) .campo("abv") .campos("nombre", "abv") .construya()); Sistema.fuera.println("nConsulta de rango numérico"); Sistema.fuera.println("totalHits " + resultado.totalHits()); para (BúsquedaQueryRow fila : resultado) { JsonDocument doc = cubo.consiga(fila.id()); Sistema.fuera.println(""" + doc.content().get("nombre") + "", abv: " + doc.contenido().consiga("abv")); } |
Qué salidas:
1 2 3 4 5 |
Numérico Gama Consulta totalHits: 62 "Stud Service Stout", abv: 3.1 "Rubia", abv: 3.0 "Luz de la montaña Locke", abv: 3.7 |
Las fechas también se cubren con el DateRangeQuery
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Calendario calendario = Calendario.getInstance(); calendario.configure(2011, Calendario.MARZO, 1); Fecha iniciar = calendario.getTime(); calendario.configure(2011, Calendario.ABRIL, 1); Fecha fin = calendario.getTime(); resultado = cubo.consulta(DateRangeQuery.en("beerIndex") .iniciar(iniciar) .fin(fin) .campo("actualizado") .campos("nombre", "actualizado") .construya()); Sistema.fuera.println("nConsulta de intervalo de fechas"); Sistema.fuera.println("totalHits " + resultado.totalHits()); para (BúsquedaQueryRow fila : resultado) { JsonDocument doc = cubo.consiga(fila.id()); Sistema.fuera.println(""" + doc.content().get("nombre") + "", actualizado: " + doc.contenido().consiga("actualizado")); } |
Qué salidas:
1 2 3 4 5 6 |
Fecha Gama Consulta totalHits: 4 "Dank", actualizado: 2011-03-16 09:06:54 "Oso", actualizado: 2011-03-16 09:05:15 "Dientes de verano", actualizado: 2011-03-08 12:22:14 "Cervecería Columbus", actualizado: 2011-03-08 12:19:07 |
Consulta genérica
FTS
también ofrecen una forma más genérica de consulta que combina frases, términos y más utilizando la función Cadena Sintaxis de consulta
. Se puede acceder a él en la API a través de la función StringQuery
.
Combinación de
Además, puede combinar criterios sencillos como MatchQuery
mediante consultas combinadas. Tomando estas dos simples consultas de términos:
1 2 |
MatchQuery bitterQuery = MatchQuery.en("beerIndex").match("amargo").campo("descripción").construya(); MatchQuery maltyQuery = MatchQuery.en("beerIndex").match("malteado").campo("descripción").construya(); |
Puedes combinarlos de diferentes maneras:
- a
conjunción
busca todos los términos
1 |
ConjunctionQuery.en("beerIndex").conjuncts(bitterQuery, maltyQuery) |
- a
disyunción
busca al menos un término
1 |
DisjunctionQuery.en("beerIndex").disyuntos(bitterQuery, maltyQuery) |
- a
consulta booleana
permite combinar los dos enfoques
1 |
BooleanQuery.en("beerIndex").debe(bitterQuery).no debe(maltyQuery) |
Obtener explicaciones de los aciertos
Si desea obtener información sobre la puntuación y el emparejamiento de un determinado BúsquedaQueryRow
puede construir su consulta utilizando la función .explain(true)
y obtener los detalles del índice en el parámetro explicación()
campo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
{"mensaje":"suma de:","niños":[{"mensaje":"producto de:","niños":[{"mensaje":"suma de:","niños":[{"mensaje":"producto de:","niños":[{"mensaje":"suma de:","niños":[ { "mensaje": "weight(_all:national^1.000000 en penn_brewery-penn_marzen), producto de:", "niños": [ { "mensaje": "queryWeight(_all:national^1.000000), producto de:", "niños": [ { "mensaje": "boost", "valor": 1 }, { "mensaje": "idf(docFreq=17, maxDocs=7303)", "valor": 7.005668743723945 }, { "mensaje": "queryNorm", "valor": 0.1427415478209491 } ], "valor": 0.9999999999999999 }, { "mensaje": "fieldWeight(_all:national in penn_brewery-penn_marzen), producto de:", "niños": [ { "mensaje": "tf(termFreq(_all:national)=1", "valor": 1 }, { "mensaje": "fieldNorm(field=_all, doc=penn_brewery-penn_marzen)", "valor": 0.10000000149011612 }, { "mensaje": "idf(docFreq=17, maxDocs=7303)", "valor": 7.005668743723945 } ], "valor": 0.7005668848116544 } ], "valor": 0.7005668848116543 } ],"valor":0.7005668848116543},{"mensaje":"coord(1/1)","valor":1}],"valor":0.7005668848116543}],"valor":0.7005668848116543},{"mensaje":"coord(1/1)","valor":1}],"valor":0.7005668848116543}],"valor":0.7005668848116543} |
Conclusión
Esperamos que este avance de la API haya despertado su interés.
Descárguese el primer Vista previa para desarrolladores de Couchbase 4.5 con servicio integrado de búsqueda de texto completo. Esperamos que pueda empezar a buscar rápidamente utilizando el servicio asociado API del SDK de Java.
Y hasta entonces... ¡Feliz codificación!
– El equipo del SDK de Java