CLOVER🍀

That was when it all began.

Apache Solr 5.xでピボットファセットを試す

以前、Apache Solrでファセットを使ったエントリを書きました。

Apache Solr 5.xでファセットを試す - CLOVER

この時は気付いていなかったのですが、Solr 4からピボットファセット(Pivot (Decision Tree) Faceting)なるものが追加されているらしいです。

Pivot (Decision Tree) Faceting

これは知りませんでした。ちょっと試してみます。

準備

利用するSolrのスキーマ定義やデータについては、以下のエントリで使用したものと同じものを使用します。

Apache Solr 5.xでファセットを試す - CLOVER

一応、再掲。

schema.xmlでのスキーマ定義。

    <field name="isbn" type="string" indexed="true" stored="true" required="true" multiValued="false" />
    <field name="title" type="text_ja" indexed="true" stored="true" multiValued="false"/>
    <field name="price" type="int" indexed="true" stored="true" multiValued="false"/>
    <field name="publish_date" type="string" indexed="true" stored="true" multiValued="false"/>
    <field name="publish_year" type="string" indexed="true" stored="false" multiValued="false"/>
    <field name="author" type="text_ja" indexed="true" stored="true" multiValued="true"/>
    <field name="author_facet" type="string" indexed="true" stored="false" multiValued="true"/>
    <field name="tag" type="text_ja" indexed="true" stored="true" multiValued="true"/>
    <field name="tag_facet" type="string" indexed="true" stored="false" multiValued="true"/>

    <copyField source="publish_date" dest="publish_year" maxChars="4"/>
    <copyField source="author" dest="author_facet"/>
    <copyField source="tag" dest="tag_facet"/>

    <uniqueKey>isbn</uniqueKey>

登録するデータ。
data.json

[
  {
    "isbn": "978-4774161631",
    "title": "[改訂新版] Apache Solr入門 〜オープンソース全文検索エンジン",
    "price": 3600,
    "publish_date": "20131129",
    "author": ["大谷 純", "阿部 慎一朗", "大須賀 稔", "北野 太郎", "鈴木 教嗣", "平賀 一昭", "株式会社リクルートテクノロジーズ"],
    "tag": ["Java", "Lucene", "Solr", "全文検索"]
  },
  {
    "isbn": "978-4048662024",
    "title": "高速スケーラブル検索エンジン ElasticSearch Server",
    "price": 2800,
    "publish_date": "20140321",
    "author": ["Rafal Kuc", "Marek Rogozinski", "大岩 達也", "大谷 純", "兼山 元太", "水戸 祐介", "守谷 純之介", "株式会社リクルートテクノロジーズ"],
    "tag": ["Java", "Lucene", "Elasticsearch", "全文検索"]
  },
  {
    "isbn": "978-4774127804",
    "title": "Apache Lucene 入門 〜Java・オープンソース・全文検索システムの構築",
    "price": 3200,
    "publish_date": "20060517",
    "author": [" 関口 宏司"],
    "tag": ["Java", "Lucene", "全文検索"]
  },
  {
    "isbn": "978-4774127804",
    "title": "Beginning Java EE 6 GlassFish 3で始めるエンタープライズJava",
    "price": 3200,
    "publish_date": "20060517",
    "author": ["Antonio Goncalves", "日本オラクル株式会社", "株式会社プロシステムエルオーシー"],
    "tag": ["Java", "Java EE", "全文検索"]
  },
  {
    "isbn": "978-4777518654",
    "title": "はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発",
    "price": 2500,
    "publish_date": "20141101",
    "author": ["槇 俊明"],
    "tag": ["Java", "Spring", "Spring Boot"]
  },
  {
    "isbn": "978-4798121963",
    "title": "エリック・エヴァンスのドメイン駆動設計",
    "price": 5200,
    "publish_date": "20110409",
    "author": ["エリック・エヴァンス", "今関 剛", "和智 右桂", "牧野 祐子"],
    "tag": ["DDD"]
  },
  {
    "isbn": "978-4798131610",
    "title": "実践ドメイン駆動設計",
    "price": 5200,
    "publish_date": "20150317",
    "author": ["ヴァーン・ヴァーノン", "高木 正弘"],
    "tag": ["DDD"]
  }
]

データ登録。

$ curl -H 'Content-Type: application/json' 'http://localhost:8983/solr/mycore/update?commit=true' --data-binary @data.json

ここからスタートです。

ピボットファセットを使う

ピボットファセットを使うには、ファセットリクエストに追加パラメータで「facet.pivot」を追加すればよいみたいです。

例えば、こんな感じ。

$ curl 'http://localhost:8983/solr/mycore/select?wt=json&indent=true&facet=true&facet.sort=index&facet.pivot=price' -d '{ "query": "*:*" }'

「facet.pivot=price」として、priceフィールドを対象にしました。

「facet=true」は必須です。「facet.sort」も、ピボットファセットの結果に影響を与えるみたいです。

通常のクエリの結果は省略しまして、ピボットファセットを含む、ファセットの結果は以下のようになります。

  "facet_counts":{
    "facet_queries":{},
    "facet_fields":{},
    "facet_dates":{},
    "facet_ranges":{},
    "facet_intervals":{},
    "facet_heatmaps":{},
    "facet_pivot":{
      "price":[{
          "field":"price",
          "value":2500,
          "count":1},
        {
          "field":"price",
          "value":2800,
          "count":1},
        {
          "field":"price",
          "value":3200,
          "count":1},
        {
          "field":"price",
          "value":3600,
          "count":1},
        {
          "field":"price",
          "value":5200,
          "count":2}]}}}

これだけだとただのファセットですが、ここから「facet.pivot」パラメータにカンマ区切りでフィールドを追加していくことで、どんどんファセットにする層を追加することができます。

「facet.pivot=price,publish_year」とした場合。

$ curl 'http://localhost:8983/solr/mycore/select?wt=json&indent=true&facet=true&facet.sort=index&facet.pivot=price,publish_year' -d '{ "query": "*:*" }'

ピボットファセットの結果。

    "facet_pivot":{
      "price,publish_year":[{
          "field":"price",
          "value":2500,
          "count":1,
          "pivot":[{
              "field":"publish_year",
              "value":"2014",
              "count":1}]},
        {
          "field":"price",
          "value":2800,
          "count":1,
          "pivot":[{
              "field":"publish_year",
              "value":"2014",
              "count":1}]},
        {
          "field":"price",
          "value":3200,
          "count":1,
          "pivot":[{
              "field":"publish_year",
              "value":"2006",
              "count":1}]},
        {
          "field":"price",
          "value":3600,
          "count":1,
          "pivot":[{
              "field":"publish_year",
              "value":"2013",
              "count":1}]},
        {
          "field":"price",
          "value":5200,
          "count":2,
          "pivot":[{
              "field":"publish_year",
              "value":"2011",
              "count":1},
            {
              "field":"publish_year",
              "value":"2015",
              "count":1}]}]}}}

priceフィールドによるファセットの配下に、publish_yearフィールドによるファセットが現れました。

より上位のファセットの結果から、さらに別フィールドのファセットが作り出せると。これは便利ですね。

「facet.pivot=price,publish_year,author_facet」とした場合。

$ curl 'http://localhost:8983/solr/mycore/select?wt=json&indent=true&facet=true&facet.sort=index&facet.pivot=price,publish_year,author_facet' -d '{ "query": "*:*" }'

結果は3段になります。

    "facet_pivot":{
      "price,publish_year,author_facet":[{
          "field":"price",
          "value":2500,
          "count":1,
          "pivot":[{
              "field":"publish_year",
              "value":"2014",
              "count":1,
              "pivot":[{
                  "field":"author_facet",
                  "value":"槇 俊明",
                  "count":1}]}]},
        {
          "field":"price",
          "value":2800,
          "count":1,
          "pivot":[{
              "field":"publish_year",
              "value":"2014",
              "count":1,
              "pivot":[{
                  "field":"author_facet",
                  "value":"Marek Rogozinski",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"Rafal Kuc",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"兼山 元太",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"大岩 達也",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"大谷 純",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"守谷 純之介",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"株式会社リクルートテクノロジーズ",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"水戸 祐介",
                  "count":1}]}]},
        {
          "field":"price",
          "value":3200,
          "count":1,
          "pivot":[{
              "field":"publish_year",
              "value":"2006",
              "count":1,
              "pivot":[{
                  "field":"author_facet",
                  "value":"Antonio Goncalves",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"日本オラクル株式会社",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"株式会社プロシステムエルオーシー",
                  "count":1}]}]},
        {
          "field":"price",
          "value":3600,
          "count":1,
          "pivot":[{
              "field":"publish_year",
              "value":"2013",
              "count":1,
              "pivot":[{
                  "field":"author_facet",
                  "value":"北野 太郎",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"大谷 純",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"大須賀 稔",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"平賀 一昭",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"株式会社リクルートテクノロジーズ",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"鈴木 教嗣",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"阿部 慎一朗",
                  "count":1}]}]},
        {
          "field":"price",
          "value":5200,
          "count":2,
          "pivot":[{
              "field":"publish_year",
              "value":"2011",
              "count":1,
              "pivot":[{
                  "field":"author_facet",
                  "value":"エリック・エヴァンス",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"今関 剛",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"和智 右桂",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"牧野 祐子",
                  "count":1}]},
            {
              "field":"publish_year",
              "value":"2015",
              "count":1,
              "pivot":[{
                  "field":"author_facet",
                  "value":"ヴァーン・ヴァーノン",
                  "count":1},
                {
                  "field":"author_facet",
                  "value":"高木 正弘",
                  "count":1}]}]}]}}}

新ファセットAPIでピボットファセットと同じことをする

ここで、Solr 5.3からファセットAPIが新しくなったことを考えると、新しいファセットAPIではピボットファセットはどうやるのか?ということになります。

答えは単純で、ファセットリクエストを入れ子にします。ドキュメントによれば、「Nested Facets」もしくは「sub-facets」と呼ぶみたいです。

https://cwiki.apache.org/confluence/display/solr/Faceted+Search

先ほどのピボットファセットの例と同じように、ひとつのフィールド指定から順次進めていく形で書いてきましょう。

まずは、priceフィールドでファセットを組んでみます。

$ curl 'http://localhost:8983/solr/mycore/select?wt=json&indent=true' -d 'q=*:*&json.facet={ prices: { terms: { field: price, sort: { index: asc } } } }'

結果は、このように。

  "facets":{
    "count":6,
    "prices":{
      "buckets":[{
          "val":2500,
          "count":1},
        {
          "val":2800,
          "count":1},
        {
          "val":3200,
          "count":1},
        {
          "val":3600,
          "count":1},
        {
          "val":5200,
          "count":2}]}}}

続いて、priceフィールドの配下にpublish_yearsフィールドでファセットを作ります。

$ curl 'http://localhost:8983/solr/mycore/select?wt=json&indent=true' -d 'q=*:*&json.facet={ prices: { terms: { field: price, sort: { index: asc }, facet: { publish_years: { terms: { field: publish_year, sort: { index: asc } } } } } } }'

json.facetの部分を整形すると、こんな感じ。

json.facet=
  {
    prices: {
      terms: {
        field: price,
        sort: { index: asc },
        facet: {
          publish_years: {
            terms: {
              field: publish_year,
              sort: { index: asc }
            }
          }
        }
      }
    }
 }

結果。

  "facets":{
    "count":6,
    "prices":{
      "buckets":[{
          "val":2500,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2014",
                "count":1}]}},
        {
          "val":2800,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2014",
                "count":1}]}},
        {
          "val":3200,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2006",
                "count":1}]}},
        {
          "val":3600,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2013",
                "count":1}]}},
        {
          "val":5200,
          "count":2,
          "publish_years":{
            "buckets":[{
                "val":"2011",
                "count":1},
              {
                "val":"2015",
                "count":1}]}}]}}}

先ほどのピボットファセットの結果と、同等のものが得られました!すごい!!

price/publish_year/author_facetの3段もやってみます。

$ curl 'http://localhost:8983/solr/mycore/select?wt=json&indent=true' -d 'q=*:*&json.facet={ prices: { terms: { field: price, sort: { index: asc }, facet: { publish_years: { terms: { field: publish_year, sort: { index: asc }, facet: { authors: { terms: { field: author_facet, sort: { index: asc } } } } } } } } } }'

結果。

  "facets":{
    "count":6,
    "prices":{
      "buckets":[{
          "val":2500,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2014",
                "count":1,
                "authors":{
                  "buckets":[{
                      "val":"槇 俊明",
                      "count":1}]}}]}},
        {
          "val":2800,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2014",
                "count":1,
                "authors":{
                  "buckets":[{
                      "val":"Marek Rogozinski",
                      "count":1},
                    {
                      "val":"Rafal Kuc",
                      "count":1},
                    {
                      "val":"兼山 元太",
                      "count":1},
                    {
                      "val":"大岩 達也",
                      "count":1},
                    {
                      "val":"大谷 純",
                      "count":1},
                    {
                      "val":"守谷 純之介",
                      "count":1},
                    {
                      "val":"株式会社リクルートテクノロジーズ",
                      "count":1},
                    {
                      "val":"水戸 祐介",
                      "count":1}]}}]}},
        {
          "val":3200,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2006",
                "count":1,
                "authors":{
                  "buckets":[{
                      "val":"Antonio Goncalves",
                      "count":1},
                    {
                      "val":"日本オラクル株式会社",
                      "count":1},
                    {
                      "val":"株式会社プロシステムエルオーシー",
                      "count":1}]}}]}},
        {
          "val":3600,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2013",
                "count":1,
                "authors":{
                  "buckets":[{
                      "val":"北野 太郎",
                      "count":1},
                    {
                      "val":"大谷 純",
                      "count":1},
                    {
                      "val":"大須賀 稔",
                      "count":1},
                    {
                      "val":"平賀 一昭",
                      "count":1},
                    {
                      "val":"株式会社リクルートテクノロジーズ",
                      "count":1},
                    {
                      "val":"鈴木 教嗣",
                      "count":1},
                    {
                      "val":"阿部 慎一朗",
                      "count":1}]}}]}},
        {
          "val":5200,
          "count":2,
          "publish_years":{
            "buckets":[{
                "val":"2011",
                "count":1,
                "authors":{
                  "buckets":[{
                      "val":"エリック・エヴァンス",
                      "count":1},
                    {
                      "val":"今関 剛",
                      "count":1},
                    {
                      "val":"和智 右桂",
                      "count":1},
                    {
                      "val":"牧野 祐子",
                      "count":1}]}},
              {
                "val":"2015",
                "count":1,
                "authors":{
                  "buckets":[{
                      "val":"ヴァーン・ヴァーノン",
                      "count":1},
                    {
                      "val":"高木 正弘",
                      "count":1}]}}]}}]}}}

で、ファセットが入れ子にできるということは、ピボットファセットと違ってクエリとかも投げられそうな気がします。

というわけで、試してみました。

$ curl 'http://localhost:8983/solr/mycore/select?wt=json&indent=true' -d 'q=*:*&json.facet={ prices: { terms: { field: price, sort: { index: asc }, facet: { publish_years: { terms: { field: publish_year, sort: { index: asc }, facet: { price_2000_to_3000: { query : "price: [2000 TO 3000]" }, price_3001_to_4000: { query : "price: [3001 TO 4000]" }, price_4001_to_5000: { query : "price: [4001 TO 5000]" }, price_5001_to_6000: { query : "price: [5001 TO 6000]" } } } } } } } }'

json.facetの部分を整形すると、こんな感じ。

json.facet=
  { prices: {
      terms: {
        field: price,
        sort: { index: asc },
        facet: { publish_years: {
          terms: {
            field: publish_year,
            sort: { index: asc },
            facet: {
              price_2000_to_3000: { query : "price: [2000 TO 3000]" },
              price_3001_to_4000: { query : "price: [3001 TO 4000]" },
              price_4001_to_5000: { query : "price: [4001 TO 5000]" },
              price_5001_to_6000: { query : "price: [5001 TO 6000]" }
            }
          }
        }
      }
    }
  }
 }

priceフィールドの下に、publish_yearフィールドに範囲入れたクエリを投げています。

結果。

  "facets":{
    "count":6,
    "prices":{
      "buckets":[{
          "val":2500,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2014",
                "count":1,
                "price_2000_to_3000":{
                  "count":1},
                "price_3001_to_4000":{
                  "count":0},
                "price_4001_to_5000":{
                  "count":0},
                "price_5001_to_6000":{
                  "count":0}}]}},
        {
          "val":2800,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2014",
                "count":1,
                "price_2000_to_3000":{
                  "count":1},
                "price_3001_to_4000":{
                  "count":0},
                "price_4001_to_5000":{
                  "count":0},
                "price_5001_to_6000":{
                  "count":0}}]}},
        {
          "val":3200,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2006",
                "count":1,
                "price_2000_to_3000":{
                  "count":0},
                "price_3001_to_4000":{
                  "count":1},
                "price_4001_to_5000":{
                  "count":0},
                "price_5001_to_6000":{
                  "count":0}}]}},
        {
          "val":3600,
          "count":1,
          "publish_years":{
            "buckets":[{
                "val":"2013",
                "count":1,
                "price_2000_to_3000":{
                  "count":0},
                "price_3001_to_4000":{
                  "count":1},
                "price_4001_to_5000":{
                  "count":0},
                "price_5001_to_6000":{
                  "count":0}}]}},
        {
          "val":5200,
          "count":2,
          "publish_years":{
            "buckets":[{
                "val":"2011",
                "count":1,
                "price_2000_to_3000":{
                  "count":0},
                "price_3001_to_4000":{
                  "count":0},
                "price_4001_to_5000":{
                  "count":0},
                "price_5001_to_6000":{
                  "count":1}},
              {
                "val":"2015",
                "count":1,
                "price_2000_to_3000":{
                  "count":0},
                "price_3001_to_4000":{
                  "count":0},
                "price_4001_to_5000":{
                  "count":0},
                "price_5001_to_6000":{
                  "count":1}}]}}]}}}

やっぱりできました!
これは便利そうですね。

参考)
Solr Sub-Facets and Statistics
Solr 5.1 Features
Solr Facet Performance
Solr4.0のPivotFacetingを使ってみる
Solr1.4系、Solr3.x系におけるPivot Facet検索について下記によい記事があったのでその抄訳です。 http://loose-bits.com/2011/09/20/pivot-facets-solr.html · GitHub