Apache Solr

 

solrjでfacet field検索

solrjでfacet field(ファセットフィールド)検索をします。solrjのファセット検索の返り値が特殊なので注意しましょう。

新サイト、tree-mapsを公開しました!!

tree-maps: 地図のWEB TOOLの事ならtree-mapsにお任せ!

地図に関するWEB TOOL専門サイトです!!

大画面で大量の緯度経度を一気にプロット、ジオコーディング、DMS<->DEGの相互変換等ができます!

◯ 広告

solrを使う理由の一つとなり得る機能、facet fieldを紹介します。

facet fieldは、MySQLでいうとことろのselect count(*)を一度に大量に行うことができます。

例えば、47都道府県別に市区町村がそれぞれ何件あるか、等のカウントを行う事ができます。

カウントをするだけの機能ですが、そのカウント速度がMySQLの数十倍〜数百倍高速なのです。

以下は、住所.jpの住所データを検索する例です。

住所データの中で、各都道府県に何件データがあるかをfacet fieldで検索しています。

import java.util.List;

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.common.params.FacetParams;

import tree.solr.search.BaseSearcher;
import tree.solr.search.SolrSearchResult;

/**
 * 住所サーチャーです。
 * @author tree
 */
public class AddressSearcher extends BaseSearcher<AddressDocument> {

    public static void main(String[] args) {
        AddressSearcher addressSearcher = SingletonS2Container.getComponent(AddressSearcher.class);
        SolrSearchResult<AddressDocument> result = addressSearcher.getPrefCount();
        Map<String, Long> map = result.getFacetField();
        for (String key : map.keySet()) {
            LOGGER.info("name={}、value={}", key, map.get(key));
        }
    }

    public SolrSearchResult<AddressDocument> getPrefCount() {
        SolrQuery solrQuery = new SolrQuery();
        solrQuery.setQuery("*:*");
        solrQuery.setFacet(true);
        solrQuery.addFacetField(AddressDocumentNames.PREF_CD);
        solrQuery.setFacetSort(FacetParams.FACET_SORT_INDEX);
        return getResultList(solrQuery, AddressDocument.class);
    }
}

↓↓↓ 実行結果です ↓↓↓
2012/11/01 12:13:39:106 INFO - AddressSearcherTest.getPrefCount name=1、value=10276
2012/11/01 12:13:39:106 INFO - AddressSearcherTest.getPrefCount name=2、value=2748
2012/11/01 12:13:39:106 INFO - AddressSearcherTest.getPrefCount name=3、value=2275
2012/11/01 12:13:39:106 INFO - AddressSearcherTest.getPrefCount name=4、value=3750
2012/11/01 12:13:39:106 INFO - AddressSearcherTest.getPrefCount name=5、value=2306
2012/11/01 12:13:39:107 INFO - AddressSearcherTest.getPrefCount name=6、value=2108
2012/11/01 12:13:39:107 INFO - AddressSearcherTest.getPrefCount name=7、value=4300
2012/11/01 12:13:39:107 INFO - AddressSearcherTest.getPrefCount name=8、value=3138
2012/11/01 12:13:39:107 INFO - AddressSearcherTest.getPrefCount name=9、value=2060
2012/11/01 12:13:39:107 INFO - AddressSearcherTest.getPrefCount name=10、value=1758
2012/11/01 12:13:39:107 INFO - AddressSearcherTest.getPrefCount name=11、value=3577
2012/11/01 12:13:39:107 INFO - AddressSearcherTest.getPrefCount name=12、value=4128
2012/11/01 12:13:39:108 INFO - AddressSearcherTest.getPrefCount name=13、value=8497
2012/11/01 12:13:39:108 INFO - AddressSearcherTest.getPrefCount name=14、value=3178
2012/11/01 12:13:39:108 INFO - AddressSearcherTest.getPrefCount name=15、value=6009
2012/11/01 12:13:39:108 INFO - AddressSearcherTest.getPrefCount name=16、value=3596
2012/11/01 12:13:39:108 INFO - AddressSearcherTest.getPrefCount name=17、value=3059
2012/11/01 12:13:39:108 INFO - AddressSearcherTest.getPrefCount name=18、value=2534
2012/11/01 12:13:39:108 INFO - AddressSearcherTest.getPrefCount name=19、value=1091
2012/11/01 12:13:39:109 INFO - AddressSearcherTest.getPrefCount name=20、value=2259
2012/11/01 12:13:39:109 INFO - AddressSearcherTest.getPrefCount name=21、value=3769
2012/11/01 12:13:39:109 INFO - AddressSearcherTest.getPrefCount name=22、value=3687
2012/11/01 12:13:39:109 INFO - AddressSearcherTest.getPrefCount name=23、value=9123
2012/11/01 12:13:39:109 INFO - AddressSearcherTest.getPrefCount name=24、value=2769
2012/11/01 12:13:39:109 INFO - AddressSearcherTest.getPrefCount name=25、value=2025
2012/11/01 12:13:39:109 INFO - AddressSearcherTest.getPrefCount name=26、value=7532
2012/11/01 12:13:39:110 INFO - AddressSearcherTest.getPrefCount name=27、value=4559
2012/11/01 12:13:39:110 INFO - AddressSearcherTest.getPrefCount name=28、value=5651
2012/11/01 12:13:39:110 INFO - AddressSearcherTest.getPrefCount name=29、value=2087
2012/11/01 12:13:39:110 INFO - AddressSearcherTest.getPrefCount name=30、value=1729
2012/11/01 12:13:39:110 INFO - AddressSearcherTest.getPrefCount name=31、value=1492
2012/11/01 12:13:39:110 INFO - AddressSearcherTest.getPrefCount name=32、value=1322
2012/11/01 12:13:39:110 INFO - AddressSearcherTest.getPrefCount name=33、value=2562
2012/11/01 12:13:39:110 INFO - AddressSearcherTest.getPrefCount name=34、value=2659
2012/11/01 12:13:39:111 INFO - AddressSearcherTest.getPrefCount name=35、value=2068
2012/11/01 12:13:39:111 INFO - AddressSearcherTest.getPrefCount name=36、value=1922
2012/11/01 12:13:39:111 INFO - AddressSearcherTest.getPrefCount name=37、value=978
2012/11/01 12:13:39:111 INFO - AddressSearcherTest.getPrefCount name=38、value=2051
2012/11/01 12:13:39:111 INFO - AddressSearcherTest.getPrefCount name=39、value=1863
2012/11/01 12:13:39:111 INFO - AddressSearcherTest.getPrefCount name=40、value=4104
2012/11/01 12:13:39:111 INFO - AddressSearcherTest.getPrefCount name=41、value=983
2012/11/01 12:13:39:112 INFO - AddressSearcherTest.getPrefCount name=42、value=2109
2012/11/01 12:13:39:112 INFO - AddressSearcherTest.getPrefCount name=43、value=2183
2012/11/01 12:13:39:112 INFO - AddressSearcherTest.getPrefCount name=44、value=2020
2012/11/01 12:13:39:112 INFO - AddressSearcherTest.getPrefCount name=45、value=1071
2012/11/01 12:13:39:112 INFO - AddressSearcherTest.getPrefCount name=46、value=1735
2012/11/01 12:13:39:112 INFO - AddressSearcherTest.getPrefCount name=47、value=1034

facet fieldのポイントは、通常のクエリが最低1個は必要な点です。

例では「*:*」という検索条件を使っています。

ステータスのフィールドを1個持たせておけば、必ず1個facet fieldで指定できるのでおすすめです。

ステータスのフィールドは、facet fieldだけでなくfacet queryも同様の理由で使えます。

ソートは「FacetParams.FACET_SORT_INDEX)」を指定するとfacet fieldの名称順、この場合は都道府県コード順に並びます。

ソートは「FacetParams.FACET_SORT_COUNT)」を指定するとfacet fieldのカウント数順に並びます。

package tree.solr.search;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

/**
 * solrの検索結果オブジェクト
 * @author tree
 * @param <T>
 */
public class SolrSearchResult<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 検索結果数
     */
    private Long numDocs;

    /**
     * クエリの実行時間
     */
    private Long elapsedTime;

    /**
     * クエリ文字列
     */
    private String query;

    /**
     * ドキュメントのリスト
     */
    private List<T> resultList;

    /**
     * ファセットフィールド
     */
    private Map<String, Long> facetField;

    /**
     * ファセットクエリー
     */
    private Map<String, Integer> facetQuery;

    public Long getNumDocs() {
        return numDocs;
    }

    public void setNumDocs(Long numDocs) {
        this.numDocs = numDocs;
    }

    public Long getElapsedTime() {
        return elapsedTime;
    }

    public void setElapsedTime(Long elapsedTime) {
        this.elapsedTime = elapsedTime;
    }

    public String getQuery() {
        return query;
    }

    public void setQuery(String query) {
        this.query = query;
    }

    public List<T> getResultList() {
        return resultList;
    }

    public void setResultList(List<T> resultList) {
        this.resultList = resultList;
    }

    public Map<String, Long> getFacetField() {
        return facetField;
    }

    public void setFacetField(Map<String, Long> facetField) {
        this.facetField = facetField;
    }

    public Map<String, Integer> getFacetQuery() {
        return facetQuery;
    }

    public void setFacetQuery(Map<String, Integer> facetQuery) {
        this.facetQuery = facetQuery;
    }
}

このクラスはサンプルなので適当です。

package tree.solr.search;

import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocumentList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import tree.solr.exception.SolrException;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * サーチャー基底クラスです。
 * @author tree
 * @param <T>
 */
public abstract class BaseSearcher<T> extends BaseServer {

    private static Logger LOGGER = LoggerFactory.getLogger(BaseSearcher.class);

    /**
     * クエリを実行します。
     * @param solrQuery クエリ
     * @param t 返り値のクラス
     * @return SolrSearchResult
     */
    protected SolrSearchResult<T> getResultList(final SolrQuery solrQuery, Class<T> t) {
        QueryResponse rsp = null;
        try {
            rsp = getSolrServer().query(solrQuery);
        } catch (SolrServerException e) {
            LOGGER.error(e.getMessage(), e);
            throw new SolrException(e);
        }
        return new SolrSearchHandler<T>() {

            @Override
            public SolrSearchResult<T> handle(QueryResponse rsp, Class<T> t) {
                SolrSearchResult<T> result = new SolrSearchResult<T>();
                SolrDocumentList doc = rsp.getResults();
                // 実行時間
                result.setElapsedTime(rsp.getElapsedTime());
                // 検索結果数
                result.setNumDocs(doc.getNumFound());
                // クエリ
                result.setQuery(solrQuery.getQuery());
                // 検索結果
                result.setResultList(rsp.getBeans(t));
                // ファセットフィールド
                List<FacetField> facetFields = rsp.getFacetFields();
                if (CollectionUtils.isNotEmpty(facetFields)) {
                    Map<String, Long> map = null;
                    if (Strings.isNullOrEmpty(solrQuery.getFacetSortString())) {
                        // ファセットソート無し
                        map = Maps.newHashMap();
                    } else {
                        // ファセットソート有り
                        map = Maps.newLinkedHashMap();
                    }
                    for (FacetField facetField : facetFields) {
                        List<Count> count = facetField.getValues();
                        for (Count c : count) {
                            map.put(c.getName(), Long.valueOf(c.getCount()));
                        }
                    }
                    result.setFacetField(map);
                }
                // ファセットクエリー
                Map<String, Integer> facetQuery = rsp.getFacetQuery();
                if (facetQuery != null)
                    result.setFacetQuery(facetQuery);
                LOGGER.info(result.getQuery());
                return result;
            }

        }.handle(rsp, t);
    }
}

このクラスはサンプルなので適当です。

ファセットソートが指定された場合、LinkedHashMapで要素の順番を保持するようにしています。

package tree.solr.search;

import java.io.IOException;

import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;

import tree.solr.exception.SolrException;

/**
 * SolrServer基底クラスです。
 * @author tree
 */
public abstract class BaseServer {

    private SolrServer server;

    /**
     * SolrServerを設定します。<br>
     * solr.diconでインジェクションするためのメソッドです。
     * @param server SolrServer
     */
    public void setSolrServer(SolrServer server) {
        this.server = server;
    }

    /**
     * SolrServerを取得します。
     * @return
     */
    protected SolrServer getSolrServer() {
        return server;
    }

    /**
     * SolrServerにpingします。
     */
    protected void ping() {
        try {
            server.ping();
        } catch (SolrServerException e) {
            throw new SolrException(e);
        } catch (IOException e) {
            throw new SolrException(e);
        }
    }
}

このクラスはサンプルなので適当です。

◯ 広告