Apache Solr

 

solrjでfacet query検索

solrjでfacet query(ファセットクエリー)検索をします。solrjのファセット検索の返り値が特殊なので注意しましょう。

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

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

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

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

◯ 広告

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

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

クエリーに対する結果数を一気に取得する事ができます。

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

facet fieldは、通常のクエリーの条件を満たす「フィールド」の件数を取得します。

facet queryは、通常のクエリー+複数の「クエリー」の結果の件数を取得します。

facet fieldはシンプルな件数取得で、facet queryは複雑なクエリーの結果の件数を取得する事ができます。

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

住所データの中で、各都道府県に大字通称名が「北から始まる」データを検索しています。

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.getFirstStreet();
        Map<String, Integer> map = result.getFacetQuery();
        for (String key : map.keySet()) {
            LOGGER.info("name={}、value={}", key, map.get(key));
        }
    }

    public SolrSearchResult<AddressDocument> getFirstStreet() {
        SolrQuery solrQuery = new SolrQuery();
        solrQuery.setQuery("*:*");
        solrQuery.setFacet(true);
        // 47都道府県
        for (int i = 1; i <= 47; i++) {
            solrQuery.addFacetQuery("+" + AddressDocumentNames.PREF_CD + ":" + i + " +"
                    + AddressDocumentNames.TOWN_AREA_NAME + ":北*");
        }
        solrQuery.setFacetSort(FacetParams.FACET_SORT_INDEX);
        return getResultList(solrQuery, AddressDocument.class);
    }
}

↓↓↓ 実行結果です ↓↓↓
2012/11/02 01:24:02:869 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:1 +town_area_name_str:北*、value=995
2012/11/02 01:24:02:869 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:2 +town_area_name_str:北*、value=20
2012/11/02 01:24:02:869 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:3 +town_area_name_str:北*、value=28
2012/11/02 01:24:02:869 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:4 +town_area_name_str:北*、value=43
2012/11/02 01:24:02:869 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:5 +town_area_name_str:北*、value=22
2012/11/02 01:24:02:869 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:6 +town_area_name_str:北*、value=33
2012/11/02 01:24:02:869 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:7 +town_area_name_str:北*、value=109
2012/11/02 01:24:02:869 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:8 +town_area_name_str:北*、value=39
2012/11/02 01:24:02:870 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:9 +town_area_name_str:北*、value=23
2012/11/02 01:24:02:870 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:10 +town_area_name_str:北*、value=26
2012/11/02 01:24:02:870 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:11 +town_area_name_str:北*、value=75
2012/11/02 01:24:02:870 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:12 +town_area_name_str:北*、value=55
2012/11/02 01:24:02:870 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:13 +town_area_name_str:北*、value=85
2012/11/02 01:24:02:870 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:14 +town_area_name_str:北*、value=35
2012/11/02 01:24:02:870 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:15 +town_area_name_str:北*、value=79
2012/11/02 01:24:02:870 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:16 +town_area_name_str:北*、value=36
2012/11/02 01:24:02:871 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:17 +town_area_name_str:北*、value=30
2012/11/02 01:24:02:871 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:18 +town_area_name_str:北*、value=54
2012/11/02 01:24:02:871 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:19 +town_area_name_str:北*、value=9
2012/11/02 01:24:02:871 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:20 +town_area_name_str:北*、value=38
2012/11/02 01:24:02:871 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:21 +town_area_name_str:北*、value=34
2012/11/02 01:24:02:871 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:22 +town_area_name_str:北*、value=36
2012/11/02 01:24:02:871 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:23 +town_area_name_str:北*、value=132
2012/11/02 01:24:02:871 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:24 +town_area_name_str:北*、value=68
2012/11/02 01:24:02:872 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:25 +town_area_name_str:北*、value=31
2012/11/02 01:24:02:872 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:26 +town_area_name_str:北*、value=147
2012/11/02 01:24:02:872 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:27 +town_area_name_str:北*、value=112
2012/11/02 01:24:02:872 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:28 +town_area_name_str:北*、value=98
2012/11/02 01:24:02:872 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:29 +town_area_name_str:北*、value=56
2012/11/02 01:24:02:872 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:30 +town_area_name_str:北*、value=54
2012/11/02 01:24:02:872 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:31 +town_area_name_str:北*、value=9
2012/11/02 01:24:02:872 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:32 +town_area_name_str:北*、value=5
2012/11/02 01:24:02:873 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:33 +town_area_name_str:北*、value=26
2012/11/02 01:24:02:873 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:34 +town_area_name_str:北*、value=10
2012/11/02 01:24:02:873 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:35 +town_area_name_str:北*、value=14
2012/11/02 01:24:02:873 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:36 +town_area_name_str:北*、value=23
2012/11/02 01:24:02:873 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:37 +town_area_name_str:北*、value=5
2012/11/02 01:24:02:873 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:38 +town_area_name_str:北*、value=44
2012/11/02 01:24:02:873 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:39 +town_area_name_str:北*、value=26
2012/11/02 01:24:02:873 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:40 +town_area_name_str:北*、value=45
2012/11/02 01:24:02:874 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:41 +town_area_name_str:北*、value=30
2012/11/02 01:24:02:874 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:42 +town_area_name_str:北*、value=14
2012/11/02 01:24:02:874 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:43 +town_area_name_str:北*、value=18
2012/11/02 01:24:02:874 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:44 +town_area_name_str:北*、value=16
2012/11/02 01:24:02:874 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:45 +town_area_name_str:北*、value=59
2012/11/02 01:24:02:874 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:46 +town_area_name_str:北*、value=6
2012/11/02 01:24:02:874 INFO - AddressSearcherTest.getFirstStreet name=+pref_cd_int:47 +town_area_name_str:北*、value=8

facet queryのポイントは、通常のクエリー+ファセットクエリーな点です。

コンソールログを見ると解りますが、ファセットクエリーはaddFacetQueryメソッドで沢山クエリを生成します。

ソートはfacet fieldと同じで、「FacetParams.FACET_SORT_INDEX」「FacetParams.FACET_SORT_COUNT」の2種類です。

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);
        }
    }
}

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

◯ 広告