2014-12-18 13 views
8

Sto implementando un indice di completamento automatico in ElasticSearch e ho riscontrato un problema con l'ordinamento/il punteggio. Dire che ho le seguenti stringhe in un indice:Punteggio per posizione di termine in ElasticSearch?

apple banana coconut donut 
apple banana donut durian 
apple donut coconut durian 
donut banana coconut durian 

Quando cerco per "ciambella", voglio i risultati da ordinare dalla posizione termine in questo modo:

donut banana coconut durian 
apple donut coconut durian 
apple banana donut durian 
apple banana coconut donut 

non posso capire come farlo accadere. La posizione di termine non è inclusa nella logica di punteggio predefinita e non riesco a trovare un modo per ottenerla. Sembra un problema abbastanza semplice anche se altri devono averlo incontrato prima. Qualcuno l'ha ancora capito?

Grazie!

+0

Forse questo aiuterà http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-advanced-scripting.html –

+0

Avrei iniziato a seguire questa strada fino a quando ho scoperto che lo script non ha accesso alla stringa di ricerca con token :( – IGx89

risposta

0

Ecco la soluzione che ho finito con, in base alla risposta di Andrei e ampliato per supportare più termini di ricerca e di punteggio aggiuntivo in base alla durata della prima parola nel risultato:

In primo luogo, definire la seguente analizzatore personalizzato (si mantiene l'intera stringa come un unico token e in minuscolo esso):

"raw_analyzer": { 
    "type": "custom", 
    "filter": [ 
     "lowercase" 
    ], 
    "tokenizer": "keyword" 
} 

in secondo luogo, definire la mappatura campo di ricerca in questo modo ("nome" della miniera di nome):

"name": { 
    "type": "string", 
    "analyzer": "english", 
    "fields": { 
     "raw": { 
      "type": "string", 
      "index_analyzer": "raw_analyzer", 
      "search_analyzer": "standard" 
     } 
    } 
}, 
"_nameFirstWordLength": { 
    "type": "long" 
} 

In terzo luogo, durante il popolamento dell'indice utilizzare la seguente logica (la mia è in C#) per popolare:

_nameFirstWordLength = fi.Name.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)[0].Length 

Infine, fare la tua ricerca come segue:

{ 
    "query":{ 
     "bool":{ 
     "must":{ 
      "match_phrase_prefix":{ 
       "name":{ 
        "query":"apple" 
       } 
      } 
     }, 
     "should":{ 
      "function_score":{ 
       "query":{ 
        "query_string":{ 
        "fields":[ 
         "name.raw" 
        ], 
        "query":"apple*" 
        } 
       }, 
       "script_score":{ 
        "script":"100/doc['_nameFirstWordLength'].value" 
       }, 
       "boost_mode":"replace" 
      } 
     } 
     } 
    } 
} 

sto usando match_phrase_prefix in modo che parziale le corrispondenze sono supportate, come "ap" corrispondente a "apple". Il bool deve/dovrebbe con quella seconda query query_string su name.raw dare un punteggio più alto ai risultati il ​​cui nome inizia con uno dei termini di ricerca (nel mio codice sto pre-processando la stringa di ricerca, solo per quella seconda query, a aggiungi un "*" dopo ogni parola). Infine, il wrapping di questa seconda query in uno script function_score che utilizza il valore di _nameFirstWordLength fa sì che i risultati aggiornati dalla seconda query vengano ulteriormente ordinati in base alla lunghezza della prima parola (ad esempio che Apple mostra prima di Applebee, ad esempio).

5

Si può fare un ordinamento personalizzato, in questo modo:

{ 
    "query": { 
    "match": { 
     "content": "donut" 
    } 
    }, 
    "sort": { 
    "_script": { 
     "script": "termInfo=_index['content'].get('donut',_OFFSETS);for(pos in termInfo){return _score+pos.startOffset};", 
     "type": "number", 
     "order": "asc" 
    } 
    } 
} 

In lì ho appena tornato il startOffset. Se hai bisogno di qualcos'altro, gioca con quei valori e il punteggio originale e ottieni un valore confortevole per le tue esigenze.

Oppure si può fare qualcosa di simile:

{ 
    "query": { 
    "function_score": { 
     "query": { 
     "match": { 
      "content": "donut" 
     } 
     }, 
     "script_score": { 
     "script": "termInfo=_index['content'].get('donut',_OFFSETS);for(pos in termInfo){return pos.startOffset};" 
     }, 
     "boost_mode": "replace" 
    } 
    }, 
    "sort": [ 
    { 
     "_score": "asc" 
    } 
    ] 
} 

In entrambi i casi è necessario nella vostra mappatura per quel campo specifico di avere questo:

"content": { 
    "type": "string", 
    "index_options": "offsets" 
} 

significa index_options deve essere impostato a offsets . Here maggiori dettagli su questo.

+0

Grazie Andrei! Risposta completa e approfondita :). Funzionerebbe quasi, tranne che sto usando la derivazione, quindi se ho cercato, per esempio, per "apple" non sarebbe il termine di ricerca nell'indice (perché il termine indicizzato è "appl"). Inoltre, non sarebbe ideale per i termini di ricerca con più parole, anche se probabilmente potrei aggirarlo. – IGx89

+0

In questo caso - con stemmer - dovrebbe essere semplice: trasforma il tuo campo in un 'multi_field'. Fai qualsiasi ricerca desideri sulla parte con stemmed con un sotto-campo e il punteggio personalizzato sopra sulla parte non a gambo: '" content ": { " type ":" multi_field ", " fields ": { " content ": { "type": "stringa", "analizzatore": "inglese" }, "content_no_stemmer": { "tipo": "stringa", "index_options": "offset" } } } ' –

+0

E lo script cambierà in' "termInfo = _index ['content.content_no_stemmer']. Get ('apple', _ OFFSETS) ....' Questo lavoro per te? –

Problemi correlati