W poprzednich częściach przedstawiliśmy dwie metody tworzenia podpowiadania zapytań. Następnie jedną z nich rozbudowaliśmy o możliwość dodatkowego definiowania zwracanych informacji. W tym artykule wrócimy ponownie wykorzystamy faceting oraz ngram.
Wymagania
Przy tworzeniu listy podpowiedzi przyjęliśmy następujące założenia:
- Podpowiadana jest cała fraza a nie tylko pojedyncze słowo
- Podpowiadana fraza może wystąpić w indeksie wielokrotnie
- w wyniku chcemy znać liczbę wystąpień
- Częściej występujące frazy są podpowiadane w pierwszej kolejności
- Kolejność podawanych przez użytkownika słów nie musi odpowiadać kolejności występowania słów w podpowiadanej frazie
Rozwiązanie
Podany w pierwszej części sposób odpada ze względu na pierwsze założenie. Co prawda wyszukiwanie słów we frazie da się prosto osiągnąć zmieniając sposób analizy, jednak zwracane są pojedyncze słowa a nie cała fraza.
Rozwiązanie to zmodyfikowana wersja sposobu z facetingiem. Zamiast stosować wyszukiwanie wszystkich elementów i zawężanie wyników facetingu poprzez facet.prefix, możemy od razu wyszukać tylko te elementy, które mają fragment słowa wpisanego przez użytkownika. Ponieważ nie chcemy stosować zapytania prefiksowego ( „słowo*” ) ze względów wydajnościowych, na pomoc wezwiemy ngramy. Oznacza to zapisanie w indeksie wszystkich przedrostków słowa. Oczywistą wadą jest rozrost indeksu, ale w naszym przypadku jesteśmy w stanie z tym żyć 🙂
Schema.xml
Definiujemy więc dodatkowy typ:
<fieldType name="text_autocomplete" class="solr.TextField" positionIncrementGap="100"> <analyzer type="index"> <tokenizer class="solr.WhitespaceTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.EdgeNGramFilterFactory" minGramSize="1" maxGramSize="25" /> </analyzer> <analyzer type="query"> <tokenizer class="solr.WhitespaceTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType>
Oraz pola: to, którego wartość będziemy wyświetlać oraz te, służące do wyszukiwania:
<field name="tag_autocomplete" type="text_autocomplete" indexed="true" stored="true" omitNorms="true" omitTermFreqAndPositions="true"/> <field name="tag" type="string" indexed="true" stored="true" />
Zostaje jeszcze odpowiedni copyField:
<copyField source="tag" dest="tag_autocomplete"/>
Zapytanie
Po przeindeksowaniu możemy przystąpić do tworzenia zapytania:
- Zawężamy listę wyników do tych, które w polu tag_autocomplete mają poszukiwany fragment słowa: q=tag_autocomplete:(FRAZA)
- W przypadku wielu podanych przez użytkownika fragmentów słów istotne jest, by były one wyszukiwane wszystkie: q.op=AND
- Tak naprawdę wyniki nie są istotne, dane odczytamy z facetingu, więc informujemy solr, że nie potrzebujemy listy wyników: rows=0
- Potrzebujemy natomiast faceting: facet=true
- W dodatku to faceting po polu w którym przechowujemy oryginalną zawartość pola podpowiedzi: facet.field=tag
- Nie interesują nas tagi, które nie zostały znalezione: facet.mincount=1
- Interesuje nas 5 wyników: facet.limit=5
Ostateczne zapytanie:
?q=tag_autocomplete:(FRAZA)&q.op=AND&rows=0&facet=true&facet.field=tag&facet.mincount=1&facet.limit=5
Jeśli parametry, które są stałe umieścimy w handlerze, jako wartości domyślne, to zapytanie sprowadza się do:
?q=tag_autocomplete:(FRAZA)
Słowo na koniec
Podstawową zaletą tego rozwiązania w stosunku do rozwiązania opartego o faceting i facet.prefix jest możliwość używania innego pola do zwracania podpowiedzi. Dzięki temu przy podpowiadaniu pojedynczych słów możemy w wyniku wyświetlić całą zawartość pola „tag”.