Autocomplete na polach wielowartościowych (highlighting)

Jednym z tematów na który natknąłem się ostatnio był problem funkcjonalności autocomplete na polach, które oznaczone są jako multiValued=”true” (m.in. pytanie zadano tutaj na Stack Overflow). Przyjrzyjmy się zatem jakie mamy możliwości.

Wiele rdzeni kontra jeden rdzeń

Jedną z możliwości, jaką powinniśmy rozważyć na początku, to kwestia tego, czy jesteśmy w stanie stworzyć rdzeń lub kolekcję odpowiedzialną tylko i wyłączenie za autocomplete. Jeżeli tak, to powinniśmy iść tą drogą. Przyczyny tego są proste – taka kolekcja będzie z reguły mniejsza, niż ta w której indeksowane są nasze dane, ilość termów także ma szansę być mniejsza, a tym samym wykonywanie zapytań powinno być szybsze. Oczywiście, idzie za tym konieczność przygotowania konfiguracji, konieczność indeksacji drugiej kolekcji. Czasami jednak istnieją sytuacje kiedy takie rozwiązanie nie jest możliwe, na przykład ze względu na dodatkowe filtrowanie i takim przypadkiem zajmę się w tym wpisie.

Załóżmy dodatkowo że chcemy podpowiadać pełne frazy.

Konfiguracja

Zacznijmy zatem od konfiguracji.

Struktura indeksu

Załóżmy, że chcemy podpowiadać frazy z indeksu, oczywiście z pola wielowartościowego. Niech pole to nazywa się features, a cała konfiguracja pól w indeksie będzie następująca:

<fields>
 <field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
 <field name="features" type="string" indexed="true" stored="true" multiValued="true"/>
 <field name="features_autocomplete" type="text_autocomplete" indexed="true" stored="true" multiValued="true"/>

 <field name="_version_" type="long" indexed="true" stored="true"/>
</fields>

Jak widać do autocomplete będziemy wykorzystywać pole features_autocomplete. Pole _version_ wymagane jest przez niektóre funkcjonalności Solr 4.0 i nowszych i dlatego jest obecne w naszym indeksie.

Kopiowanie

Dodatkowo, aby automatycznie zasilać danymi pole features_autocomplete skorzystamy z funkcjonalności copy field, a zatem do pliku schema.xml dodajemy następujący wpis:

<copyField source="features" dest="features_autocomplete"/>

Typ text_autocomplete

Przyjrzyjmy się teraz jak wygląda nasz typ text_autocomplete:

<fieldType name="text_autocomplete" class="solr.TextField" positionIncrementGap="100">
 <analyzer type="index">
  <tokenizer class="solr.KeywordTokenizerFactory"/>
  <filter class="solr.LowerCaseFilterFactory"/>
  <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="50" />
 </analyzer>
 <analyzer type="query">
  <tokenizer class="solr.KeywordTokenizerFactory" />
  <filter class="solr.LowerCaseFilterFactory"/>
 </analyzer>
</fieldType>

Jak widać w trakcie indeksowania będziemy tworzyć kolejne rozwinięcia naszej frazy zawartej w polu features_autocomplete za pomocą filtra solr.EdgeNGramFilterFactory. Minimalna długość powstałego tokoena może mieć długość 2, a najdłuższy może mieć długość 50 znaków.

Na etapie zapytania sprowadzamy frazę tylko do wspólnego mianownika za pomocą solr.LowerCaseFilterFactory i filtrów tworzonych przez tą fabrykę.

Przykładowe dane

Nasze dane wyglądają następująco:

<add>
 <doc>
  <field name="id">1</field>
  <field name="features">Multiple windows</field>
  <field name="features">Single door</field>
 </doc>
 <doc>
  <field name="id">2</field>
  <field name="features">Single window</field>
  <field name="features">Single door</field>
 </doc>
 <doc>
  <field name="id">3</field>
  <field name="features">Multiple windows</field>
  <field name="features">Multiple doors</field>
 </doc>
</add>

Podstawowe zapytania

Spójrzmy zatem na zapytania.

Na początek

Zacznijmy od prostego zapytania, które w przypadku kiedy mielibyśmy pole przechowujące pojedyncze wartości zwróciłoby nam dane, które nas interesują. Zapytanie takie mogłoby wyglądać następująco:

q=features_autocomplete:sing&fl=features_autocomplete

Wyniki

Wyniki, jakie otrzymujemy z takiego zapytania to:

<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">3</int>
  <lst name="params">
   <str name="fl">features_autocomplete</str>
   <str name="q">features_autocomplete:sing</str>
  </lst>
 </lst>
 <result name="response" numFound="2" start="0">
 <doc>
  <arr name="features_autocomplete">
   <str>Single window</str>
   <str>Single door</str>
  </arr>
 </doc>
 <doc>
  <arr name="features_autocomplete">
   <str>Multiple windows</str>
   <str>Single door</str>
  </arr>
 </doc>
 </result>
</response>

Krótki komentarz

Jak widać wyniki, jakie otrzymaliśmy nie satysfakcjonują nas, ze względu na to, że oprócz wartości, w której Solr znalazł trafienie, dostajemy także resztę danych w polu wielowartościowym. Zmodyfikujmy zatem nasze zapytanie.

Zapytanie z highlightingiem

Jak widać musimy zmienić nasze zapytanie, aby otrzymać to czego potrzebujemy. Wykorzystamy do tego highlighting.

Zmienione zapytanie

Zmieńmy zatem nasze zapytanie dodając następujący fragment:

hl=true&hl.fl=features_autocomplete&hl.simple.pre=&hl.simple.post=

Zatem całe zapytanie wygląda następująco:

q=features_autocomplete:sing&fl=features_autocomplete&hl=true&hl.fl=features_autocomplete&hl.simple.pre=&hl.simple.post=

Kilka słów o dodanych parametrach:

  • hl=true – informujemy Solr, iż chcemy korzystać z highlightingu,
  • hl.fl=features_autocomplete – określamy jakie pole ma zostać wykorzystane do highlightingu,
  • hl.simple.pre= – stwierdzamy, iż nie chcemy widzieć gdzie zaczyna się podświetlony fragment,
  • hl.simple.post= – stwierdzamy, iż nie chcemy widzieć gdzie kończy się podświetlony fragment.

Wyniki

Wyniki, które Solr zwraca na powyższe zapytanie, wyglądają następująco:

<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">4</int>
  <lst name="params">
   <str name="fl">features_autocomplete</str>
   <str name="q">features_autocomplete:sing</str>
   <str name="hl.simple.pre"/>
   <str name="hl.simple.post"/>
   <str name="hl.fl">features_autocomplete</str>
   <str name="hl">true</str>
  </lst>
 </lst>
 <result name="response" numFound="2" start="0">
 <doc>
  <arr name="features_autocomplete">
   <str>Single window</str>
   <str>Single door</str>
  </arr>
 </doc>
 <doc>
  <arr name="features_autocomplete">
   <str>Multiple windows</str>
   <str>Single door</str>
  </arr>
 </doc>
 </result>
 <lst name="highlighting">
  <lst name="2">
   <arr name="features_autocomplete">
    <str>Single window</str>
   </arr>
  </lst>
  <lst name="1">
   <arr name="features_autocomplete">
    <str>Single door</str>
   </arr>
  </lst>
 </lst>
</response>

Jak widać, w sekcji odpowiedzialnej za highlighting, otrzymaliśmy te frazy, które nas interesowały.

Podsumowanie

Należy pamiętać, iż przedstawiony sposób nie jest jedynym sposobem rozwiązania przedstawionego problemu. W kolejnym wpisie przedstawimy, jak ten sam problem można rozwiązać przy pomocy facetingu, jeżeli tylko jesteśmy w stanie zaakceptować pewne niedogodności, ale o tym w następnym wpisie dotyczącym funkcjonalności autocomplete.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *