elasticsearch java documentation
Mimic Elasticsearch MatchQuery (2)
Actualmente estoy escribiendo un programa que actualmente utiliza elasticsearch como base de datos de back-end / índice de búsqueda. Me gustaría imitar la funcionalidad del punto final /_search
, que actualmente utiliza una consulta de coincidencia:
{
"query": {
"match" : {
"message" : "Neural Disruptor"
}
}
}
Al hacer algunas consultas de muestra, se obtuvieron los siguientes resultados en una base de datos masiva de World of Warcraft :
Search Term Search Result
------------------ -----------------------
Neural Disruptor Neural Needler
Lovly bracelet Ruby Bracelet
Lovely bracelet Lovely Charm Bracelet
Después de revisar la documentación de elasticsearch, encontré que la consulta de coincidencia es bastante compleja. ¿Cuál es la manera más fácil de simular una consulta de coincidencia con solo lucene en java? (Parece estar haciendo una coincidencia aproximada, además de buscar términos)
Importar código de elasticsearch para MatchQuery (creo que org.elasticsearch.index.search.MatchQuery
) no parece ser tan fácil. Está muy incrustado en Elasticsearch y no parece algo que se pueda sacar fácilmente.
No necesito una prueba completa "Debe coincidir exactamente con lo que coincide la búsqueda de elastics", solo necesito algo cercano, o eso puede hacer coincidir / encontrar la mejor coincidencia.
Ha pasado un tiempo desde que trabajé directamente con Lucene, pero lo que quieres debería ser, inicialmente, bastante sencillo. El comportamiento básico de una consulta de Lucene es muy similar a la consulta de coincidencia ( query_string es exactamente equivalente a lucene, pero la coincidencia es muy cercana). Junté un pequeño ejemplo que funciona solo con lucene (7.2.1) si quieres probarlo. El código principal es el siguiente:
public static void main(String[] args) throws Exception {
// Create the in memory lucence index
RAMDirectory ramDir = new RAMDirectory();
// Create the analyzer (has default stop words)
Analyzer analyzer = new StandardAnalyzer();
// Create a set of documents to work with
createDocs(ramDir, analyzer);
// Query the set of documents
queryDocs(ramDir, analyzer);
}
private static void createDocs(RAMDirectory ramDir, Analyzer analyzer)
throws IOException {
// Setup the configuration for the index
IndexWriterConfig config = new IndexWriterConfig(analyzer);
config.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
// IndexWriter creates and maintains the index
IndexWriter writer = new IndexWriter(ramDir, config);
// Create the documents
indexDoc(writer, "document-1", "hello planet mercury");
indexDoc(writer, "document-2", "hi PLANET venus");
indexDoc(writer, "document-3", "howdy Planet Earth");
indexDoc(writer, "document-4", "hey planet MARS");
indexDoc(writer, "document-5", "ayee Planet jupiter");
// Close down the writer
writer.close();
}
private static void indexDoc(IndexWriter writer, String name, String content)
throws IOException {
Document document = new Document();
document.add(new TextField("name", name, Field.Store.YES));
document.add(new TextField("body", content, Field.Store.YES));
writer.addDocument(document);
}
private static void queryDocs(RAMDirectory ramDir, Analyzer analyzer)
throws IOException, ParseException {
// IndexReader maintains access to the index
IndexReader reader = DirectoryReader.open(ramDir);
// IndexSearcher handles searching of an IndexReader
IndexSearcher searcher = new IndexSearcher(reader);
// Setup a query
QueryParser parser = new QueryParser("body", analyzer);
Query query = parser.parse("hey earth");
// Search the index
TopDocs foundDocs = searcher.search(query, 10);
System.out.println("Total Hits: " + foundDocs.totalHits);
for (ScoreDoc scoreDoc : foundDocs.scoreDocs) {
// Get the doc from the index by id
Document document = searcher.doc(scoreDoc.doc);
System.out.println("Name: " + document.get("name")
+ " - Body: " + document.get("body")
+ " - Score: " + scoreDoc.score);
}
// Close down the reader
reader.close();
}
Las partes importantes para extender esto serán el analyzer y la comprensión de la sintaxis del analizador de consultas de Lucene.
Tanto el indexador como las consultas utilizan el Analyzer
para indicar cómo analizar el texto para que puedan pensar en el texto de la misma manera. Establece la forma de tokenizar (en qué dividirse, si se va a Bajar (), etc.). El StandardAnalyzer
divide en espacios y algunos otros (no tengo esto a mano) y también se aplica aLower ().
QueryParser
hará algo del trabajo por ti. Si ves arriba en mi ejemplo. Hago dos cosas, le digo al analizador cuál es el campo predeterminado y paso una cadena de hey earth
. El analizador va a convertir esto en una consulta que se parece al body:hey body:earth
. Esto buscará documentos que tengan o hey
o earth
en el body
. Se encontrarán dos documentos.
Si pasáramos hey AND earth
la consulta se analiza para que parezca +body:hey +body:earth
que requerirá que los documentos tengan ambos términos. Cero documentos serán encontrados.
Para aplicar las opciones difusas, agregue un ~
a los términos que desea que sean difusos. Entonces, si la consulta es hey~ earth
, aplicará confusión a la palabra hey
y la consulta se verá como body:hey~2 body:earth
. Se encontrarán tres documentos.
Puede escribir más directamente las consultas y el analizador aún maneja las cosas. Así que si lo pasa, hey name:/"document-1/"
(el token se divide en -
) creará una consulta como el body:hey name:"document 1"
. Se devolverán dos documentos cuando busque la frase document 1
(ya que aún se muestra en la ficha -
). Donde si lo hiciera hey name:document-1
escribe body:hey (name:document name:1)
que devuelve todos los documentos ya que todos tienen el document
como un término. Hay algunos matices para entender aquí.
Trataré de cubrir un poco más sobre cómo son similares. Consulta de coincidencia de referencia. Elastic dice que la principal diferencia será: "No admite prefijos de nombre de campo, caracteres comodín u otras características" avanzadas ". Estos probablemente se destacan más en la otra dirección.
Tanto la consulta de coincidencia como la consulta de Lucene, al trabajar con un campo analizado tomarán la cadena de consulta y le aplicarán el analizador (tokenize, toLower, etc.). Entonces ambos convertirán a HEY Earth
en una consulta que busca los términos hey
o earth
.
Una consulta de coincidencia puede establecer el operator
proporcionando "operator" : "and"
. Esto cambia nuestra consulta para buscar hey
y earth
. La analogía en lucene es hacer algo como parser.setDefaultOperator(QueryParser.Operator.AND);
Lo siguiente es la fuzziness . Ambos están trabajando con la misma configuración. Creo que la "fuzziness": "AUTO"
del elástico "fuzziness": "AUTO"
es equivalente al auto de lucene cuando se aplica ~
a una consulta (aunque creo que debes agregarlo cada término, lo que es un poco engorroso).
La consulta de términos cero parece ser una construcción elástica. Si deseara la configuración de TODO, tendría que replicar la consulta de coincidencia con todas si el analizador de consultas eliminara todos los tokens de la consulta.
La frecuencia de corte parece estar relacionada con CommonTermsQuery . No he usado esto, así que puedes tener que cavar un poco si quieres usarlo.
Lucene tiene un filtro de sinónimos que se aplica a un analizador, pero es posible que deba construir el mapa usted mismo.
Las diferencias que puedas encontrar probablemente estarán en la puntuación. Cuando corro me preguntan hey earth
contra lucene. Obtiene el documento 3 y el documento 4 ambos devueltos con una puntuación de 1.3862944
. Cuando ejecuto la consulta en la forma de:
curl -XPOST http://localhost:9200/index/_search?pretty -d ''{
"query" : {
"match" : {
"body" : "hey earth"
}
}
}''
Recibo los mismos documentos, pero con una puntuación de 1.219939
. Puedes ejecutar una explicación en ambos. En lucene imprimiendo cada documento con
System.out.println(searcher.explain(query, scoreDoc.doc));
Y en elástica consultando cada documento como
curl -XPOST http://localhost:9200/index/docs/3/_explain?pretty -d ''{
"query" : {
"match" : {
"body" : "hey earth"
}
}
}''
Tengo algunas diferencias, pero no puedo explicarlas exactamente. Realmente obtengo un valor para el documento de 1.3862944
pero el fieldLength
es diferente y eso afecta el peso.
Lo que sea que se envíe al parámetro q=
del _search
final _search
se usa tal como está en la consulta query_string
(no org.elasticsearch.index.search.MatchQuery
) que entiende la sintaxis de la expresión de Lucene .
La sintaxis del analizador de consultas se define en el proyecto de Lucene utilizando JavaCC y la gramática se puede encontrar here si desea echar un vistazo. El producto final es una clase llamada QueryParser
(ver más abajo).
La clase dentro del código fuente de ES que es responsable de analizar la cadena de consulta es QueryStringQueryParser
que delega a la clase QueryParser
de Lucene (generada por JavaCC).
Entonces, básicamente, si obtiene una cadena de consulta equivalente a lo que se pasa a _search?q=...
, puede usar esa cadena de consulta con QueryParser.parse("query-string-goes-here")
y ejecutar la Query
reified utilizando sólo a Lucene.