Hierarchiczny faceting – czyli Pivot Facet w trunk’u

W dużej ilości wdrożeń z jakimi miałem do czynienia zawsze pojawiało się pytanie – co możemy zrobić, aby uzyskać od Solr drzewiastą strukturę facetingu. Oczywiście są na to metody, jednak ich wykorzystanie polegało na modyfikacji danych i odpowiednim przetwarzaniu po stronie aplikacji. Nie było to szczególnie funkcjonalne, jak i szczególnie wygodne. Jednak kilka dni temu Solr w wersji 4.0 został wzbogacony o kod oznaczony jako SOLR-792 w systemie JIRA. Zobaczmy w takim wypadku, jak pobrać wyniki facetingu w postaci drzewa.

Ważna uwaga – funkcjonalność ta w tym momencie jest dostępna tylko i wyłącznie w wersji 4.0 Solr, czyli w wersji rozwojowej. Oznaczenie 4.0 jest oznaczeniem kodu, który znajduje się w trunk’u repozytorium SVN.

Kilka słów na początek

W wielu projektach w jakich miałem okazję zajmować się była konieczność wprowadzenia hierarchicznego facetingu. Jednym z prostszych przykładów jest wymaganie polegające na pokazaniu miejscowości w województwach i ilości dokumentów zarówno w województwach, jak i w poszczególnych miejscowościach. Do tej pory, bez zmiany struktury danych, nie było możliwości zrealizowania takiej funkcjonalności. Teraz już jest 😉

Indeksowanie

Aby nie potrzebnie nie komplikować opisywanych funkcjonalności zdecydowałem się na skorzystanie z przykładowych dokumentów XML dostępnych w katalogu /exampledocs przykładowego wdrożenia. Nie modyfikowałem także pliku schema.xml, czy solrconfig.xml, tak więc konfiguracje zostały standardowe. I tyle jeżeli chodzi o konfigurację. Tak więc możemy uruchomić indeksację (komenda wywołana z katalogu $SOLR_HOME/exampledocs/):

./post.sh *.xml

Kilka ekranów informacji i mamy zaindeksowane dane.

Mechanizm

Samo skorzystanie z hierarchicznego facetingu nie jest trudne. Twórcy Solr dali nam do dyspozycji dwa dodatkowe parametry:

  • facet.pivot – lista pól oddzielonych przecinkami, która pokazuje po jakich polach i w jakiej kolejności wyliczyć strukturę,
  • facet.pivot.mincount – minimalna ilość dokumentów, aby wynik został uwzględniony w facetingu. Wartość domyślna parametru to 1.

Spróbujmy więc.

Zapytania

Na początek próba z dwoma polami. Pobieram wszystkie dokumenty z indeksu i dodaje parametr facet.pivot=cat,inStock, czyli mówię Solr, że chce dostać wyniki hierarchicznego facetingu, gdzie pierwszym poziomem hierarchii jest pole cat, a drugim poziomem jest pole inStock. Zapytanie wygląda w następujący sposób:

http://localhost:8983/solr/select/?q=*:*&facet=true&facet.pivot=cat,inStock

Aby skrócić listing pominąłem część odpowiedzialną za wyniki wyszukiwania wraz z nagłówkiem.

<?xml version="1.0" encoding="UTF-8"?>
<response>
.
.
.
<result name="response" numFound="19" start="0"/>
<lst name="facet_counts">
  <lst name="facet_queries"/>
  <lst name="facet_fields"/>
  <lst name="facet_dates"/>
  <lst name="facet_ranges"/>
  <lst name="facet_pivot">
    <arr name="cat,inStock">
      <lst>
        <str name="field">cat</str>
        <str name="value">electronics</str>
        <int name="count">17</int>
        <arr name="pivot">
          <lst>
            <str name="field">inStock</str>
            <bool name="value">true</bool>
            <int name="count">13</int>
          </lst>
          <lst>
            <str name="field">inStock</str>
            <bool name="value">false</bool>
            <int name="count">4</int>
          </lst>
        </arr>
      </lst>
      <lst>
        <str name="field">cat</str>
        <str name="value">memory</str>
        <int name="count">6</int>
        <arr name="pivot">
          <lst>
            <str name="field">inStock</str>
            <bool name="value">true</bool>
            <int name="count">6</int>
          </lst>
        </arr>
      </lst>
      <lst>
        <str name="field">cat</str>
        <str name="value">connector</str>
        <int name="count">2</int>
        <arr name="pivot">
          <lst>
            <str name="field">inStock</str>
            <bool name="value">false</bool>
            <int name="count">2</int>
          </lst>
        </arr>
      </lst>
      <lst>
        <str name="field">cat</str>
        <str name="value">graphics card</str>
        <int name="count">2</int>
        <arr name="pivot">
          <lst>
            <str name="field">inStock</str>
            <bool name="value">false</bool>
            <int name="count">2</int>
          </lst>
        </arr>
      </lst>
      <lst>
        <str name="field">cat</str>
        <str name="value">hard drive</str>
        <int name="count">2</int>
        <arr name="pivot">
          <lst>
            <str name="field">inStock</str>
            <bool name="value">true</bool>
            <int name="count">2</int>
          </lst>
        </arr>
      </lst>
      <lst>
        <str name="field">cat</str>
        <str name="value">monitor</str>
        <int name="count">2</int>
        <arr name="pivot">
          <lst>
            <str name="field">inStock</str>
            <bool name="value">true</bool>
            <int name="count">2</int>
          </lst>
        </arr>
      </lst>
      <lst>
        <str name="field">cat</str>
        <str name="value">search</str>
        <int name="count">2</int>
        <arr name="pivot">
          <lst>
            <str name="field">inStock</str>
            <bool name="value">true</bool>
            <int name="count">2</int>
          </lst>
        </arr>
      </lst>
      <lst>
        <str name="field">cat</str>
        <str name="value">software</str>
        <int name="count">2</int>
        <arr name="pivot">
          <lst>
            <str name="field">inStock</str>
            <bool name="value">true</bool>
            <int name="count">2</int>
          </lst>
        </arr>
      </lst>
    </arr>
  </lst>
</lst>
</response>

Sama prezentacja wyników facetingu, w tym wypadku, uległa zmianie. Dla każdej wartości głównego poziomu mamy znaczniki określające pole (znacznik z atrybutem name=”field”), wartość (znacznik z atrybutem name=”value”) oraz ilość dokumentów (znacznik z atrybutem name=”count”). Następnie mamy tablicę wyników drugiego poziomu (znacznik z atrybutem name=”pivot”). Tablica ta zawiera elementy takie same jak poziom pierwszy, czyli nazwa pola, wartość w polu oraz ilość dokumentów z daną wartością.

Zobaczmy, jak mechanizm ten daje sobie radę z większą ilością zagłębienia. W tym celu zadałem następujące zapytanie do tej samej wersji Solr:

http://localhost:8983/solr/select/?q=*:*&facet=true&facet.pivot=cat,inStock,features

Jak w powyższym przypadku w pominąłem nagłówek odpowiedzi wraz z wynikami zostawiając same wyniki facetingu. Dodatkowo, ze względu na długość wyników facetingu przedstawiam wyniki tylko dla jednej kategorii głównej pomijając resztę:

<?xml version="1.0" encoding="UTF-8"?>
<response>
.
.
.
<result name="response" numFound="19" start="0"/>
<lst name="facet_counts">
  <lst name="facet_queries"/>
  <lst name="facet_fields"/>
  <lst name="facet_dates"/>
  <lst name="facet_ranges"/>
  <lst name="facet_pivot">
    <arr name="cat,inStock,features">
      <lst>
        <str name="field">cat</str>
        <str name="value">electronics</str>
        <int name="count">17</int>
        <arr name="pivot">
          <lst>
            <str name="field">inStock</str>
            <bool name="value">true</bool>
            <int name="count">13</int>
            <arr name="pivot">
              <lst>
                <str name="field">features</str>
                <str name="value">2</str>
                <int name="count">7</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">3</str>
                <int name="count">7</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">lcd</str>
                <int name="count">5</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">x</str>
                <int name="count">5</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">ca</str>
                <int name="count">4</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">latenc</str>
                <int name="count">4</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">tft</str>
                <int name="count">4</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">v</str>
                <int name="count">4</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">0</str>
                <int name="count">3</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">1</str>
                <int name="count">3</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">25</str>
                <int name="count">3</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">30</str>
                <int name="count">3</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">5</str>
                <int name="count">3</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">7</str>
                <int name="count">3</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">8</str>
                <int name="count">3</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">time</str>
                <int name="count">3</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">up</str>
                <int name="count">3</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">000</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">19</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">20</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">2336</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">27</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">275</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">6</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">75</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">activ</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">built</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">cach</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">color</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">flash</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">heat</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">heatspread</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">matrix</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">mb</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">ms</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">photo</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">resolut</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">seek</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">speed</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">spreader</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">unbuff</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">usb</str>
                <int name="count">2</int>
              </lst>
            </arr>
          </lst>
          <lst>
            <str name="field">inStock</str>
            <bool name="value">false</bool>
            <int name="count">4</int>
            <arr name="pivot">
              <lst>
                <str name="field">features</str>
                <str name="value">0</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">1</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">16</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">2</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">20</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">3</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">9</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">90</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">adapt</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">car</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">clock</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">direct</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">directx</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">dual</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">dvi</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">express</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">gddr</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">ghz</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">gl</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">gpu</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">gpuvpu</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">hdtv</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">mb</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">mhz</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">open</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">opengl</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">out</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">pci</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">power</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">vpu</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">white</str>
                <int name="count">2</int>
              </lst>
              <lst>
                <str name="field">features</str>
                <str name="value">x</str>
                <int name="count">2</int>
              </lst>
            </arr>
          </lst>
        </arr>
      </lst>
    </arr>
  </lst>
</lst>
</response>

Jak widać na zaprezentowanym przykładzie, również w tym wypadku Solr nie miał problemów z poprawnym wyliczeniem hierarchii. Sama część prezentacyjna wzbogaciła się o jeden poziom zagłębienia, który podlega tym samym zasadom co reszta poziomów.

Kilka słów na koniec

Moim zdaniem jedna z bardziej przydatnych funkcjonalności dla „zwykłego” użytkownika. Niestety na razie dostępna tylko w wersji developerskiej Solr. Nie znalazłem także informacji o tym, czy planowane jest przeniesienie tej funkcjonalności do wersji 1.5 Solr, czyli gałęzi o nazwie branch_3x w SVN. Jednak, ważne jest to, że taka funkcjonalność powstała i wcześniej, czy później użytkownicy Solr będą mogli z niej korzystać.

Dodaj komentarz

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