一. Facet 簡介java
Facet 是 solr 的高級搜索功能之一 , 能夠給用戶提供更友好的搜索體驗 . 在搜索關鍵字的同時 , 可以按照 Facet 的字段進行分組並統計 .算法
二. Facet 字段apache
1. 適宜被Facet 的字段ide
通常表明了實體的某種公共屬性 , 如商品的分類 , 商品的製造廠家 , 書籍的出版商等等 .spa
2. Facet 字段的要求component
Facet 的字段必須被索引 . 通常來講該字段無需分詞 , 無需存儲 .orm
無需分詞是由於該字段的值表明了一個總體概念 , 如電腦的品牌 」 聯想 」 表明了一個總體概念 , 若是拆成 」 聯 」,」 想 」 兩個字都不具備實際意義 . 另外該字段的值無需進行大小寫轉換等處理 , 保持其原貌便可 .server
無需存儲是由於通常而言用戶所關心的並非該字段的具體值 , 而是做爲對查詢結果進行分組的一種手段 , 用戶通常會沿着這個分組進一步深刻搜索 .xml
3. 特殊狀況索引
對於通常查詢而言 , 分詞和存儲都是必要的 . 好比 CPU 類型 」Intel 酷睿 2 雙核 P7570」, 拆分紅 」Intel」,」 酷睿 」,」P7570」 這樣一些關鍵字並分別索引 , 可能提供更好的搜索體驗 . 可是若是將 CPU 做爲 Facet 字段 , 最好不進行分詞 . 這樣就形成了矛盾 , 解決方法爲 , 將 CPU 字段設置爲不分詞不存儲 , 而後創建另一個字段爲它的 COPY, 對這個 COPY 的字段進行分詞和存儲 .
schema.xml
<types> <fieldType name="string" class="solr.StrField" omitNorms="true"/> <fieldType name="tokened" class="solr.TextField" > <analyzer> …… </analyzer> </fieldType> …… </types> <fields> <field name=」cpu」 type=」string」 indexed=」true」 stored=」false」/> <field name=」cpuCopy」 type=」 tokened」 indexed=」true」 stored=」true」/> …… </fields> <copyField source="cpu" dest="cpuCopy"/> |
三. Facet 組件
Solr 的默認 requestHandler(org.apache.solr.handler.component.SearchHandler) 已經包含了 Facet 組件 (org.apache.solr.handler.component.FacetComponent). 若是自定義 requestHandler 或者對默認的 requestHandler 自定義組件列表 , 那麼須要將 Facet 加入到組件列表中去 .
solrconfig.xml
<requestHandler name="standard" class="solr.SearchHandler" default="true"> …… <arr name="components"> <str>自定義組件名</str> <str>facet</str> …… </arr> </requestHandler> |
四. Facet 查詢
進行 Facet 查詢須要在請求參數中加入 」facet=on」 或者 」facet=true」 只有這樣 Facet 組件才起做用 .
1. Field Facet
Facet 字段經過在請求中加入 」facet.field」 參數加以聲明 , 若是須要對多個字段進行 Facet 查詢 , 那麼將該參數聲明屢次 . 好比
/select?q=聯想 &facet=on &facet.field=cpu &facet.field=videoCard |
返回結果 :
<lst name="facet_counts"> <lst name="facet_queries"/> <lst name="facet_fields"> <lst name="cpu"> <int name="Intel 酷睿2雙核 T6600">48</int> <int name="Intel 奔騰雙核 T4300">28</int> <int name="Intel 酷睿2雙核 P8700">18</int> <int name="Intel 酷睿2雙核 T6570">11</int> <int name="Intel 酷睿2雙核 T6670">11</int> <int name="Intel 奔騰雙核 T4400">9</int> <int name="Intel 酷睿2雙核 P7450">9</int> <int name="Intel 酷睿2雙核 T5870">8</int> <int name="Intel 賽揚雙核 T3000">7</int> <int name="Intel 奔騰雙核 SU4100">6</int> <int name="Intel 酷睿2雙核 P8400">6</int> <int name="Intel 酷睿2雙核 SU7300">5</int> <int name="Intel 酷睿 i3 330M">4</int> </lst> <lst name="videoCard"> <int name="ATI Mobility Radeon HD 4">63</int> <int name="NVIDIA GeForce G 105M">24</int> <int name="NVIDIA GeForce GT 240M">21</int> <int name="NVIDIA GeForce G 103M">8</int> <int name="NVIDIA GeForce GT 220M">8</int> <int name="NVIDIA GeForce 9400M G">7</int> <int name="NVIDIA GeForce G 210M">6</int> </lst> </lst> <lst name="facet_dates"/> </lst> |
各個 Facet 字段互不影響 , 且能夠針對每一個 Facet 字段設置查詢參數 . 如下介紹的參數既能夠應用於全部的 Facet 字段 , 也能夠應用於每一個單獨的 Facet 字段 . 應用於單獨的字段時經過
f.字段名.參數名=參數值 |
這種方式調用 . 好比 facet.prefix 參數應用於 cpu 字段 , 能夠採用以下形式
f.cpu.facet.prefix=Intel |
1.1 facet.prefix
表示 Facet 字段值的前綴 . 好比 」facet.field=cpu&facet.prefix=Intel」, 那麼對 cpu 字段進行 Facet 查詢 , 返回的 cpu 都是以 」Intel」 開頭的 ,」AMD」 開頭的 cpu 型號將不會被統計在內 .
1.2 facet.sort
表示 Facet 字段值以哪一種順序返回 . 可接受的值爲 true(count)|false(index,lex). true(count) 表示按照 count 值從大到小排列 . false(index,lex) 表示按照字段值的天然順序 ( 字母 , 數字的順序 ) 排列 . 默認狀況下爲 true(count). 當 facet.limit 值爲負數時 , 默認 facet.sort= false(index,lex).
1.3 facet.limit
限制 Facet 字段返回的結果條數 . 默認值爲 100. 若是此值爲負數 , 表示不限制 .
1.4 facet.offset
返回結果集的偏移量 , 默認爲 0. 它與 facet.limit 配合使用能夠達到分頁的效果 .
1.5 facet.mincount
限制了 Facet 字段值的最小 count, 默認爲 0. 合理設置該參數能夠將用戶的關注點集中在少數比較熱門的領域 .
1.6 facet.missing
默認爲 」」, 若是設置爲 true 或者 on, 那麼將統計那些該 Facet 字段值爲 null 的記錄 .
1.7 facet.method
取值爲 enum 或 fc, 默認爲 fc. 該字段表示了兩種 Facet 的算法 , 與執行效率相關 .
enum 適用於字段值比較少的狀況 , 好比字段類型爲布爾型 , 或者字段表示中國的全部省份 .Solr 會遍歷該字段的全部取值 , 並從 filterCache 裏爲每一個值分配一個 filter( 這裏要求 solrconfig.xml 裏對 filterCache 的設置足夠大 ). 而後計算每一個 filter 與主查詢的交集 .
fc( 表示 Field Cache) 適用於字段取值比較多 , 但在每一個文檔裏出現次數比較少的狀況 .Solr 會遍歷全部的文檔 , 在每一個文檔內搜索 Cache 內的值 , 若是找到就將 Cache 內該值的 count 加 1.
1.8 facet.enum.cache.minDf
當 facet.method=enum 時 , 此參數其做用 ,minDf 表示 minimum document frequency. 也就是文檔內出現某個關鍵字的最少次數 . 該參數默認值爲 0. 設置該參數能夠減小 filterCache 的內存消耗 , 但會增長總的查詢時間 ( 計算交集的時間增長了 ). 若是設置該值的話 , 官方文檔建議優先嚐試 25-50 內的值 .
2. Date Facet
日期類型的字段在文檔中很常見 , 如商品上市時間 , 貨物出倉時間 , 書籍上架時間等等 . 某些狀況下須要針對這些字段進行 Facet. 不過期間字段的取值有無限性 , 用戶每每關心的不是某個時間點而是某個時間段內的查詢統計結果 . Solr 爲日期字段提供了更爲方便的查詢統計方式 . 固然 , 字段的類型必須是 DateField( 或其子類型 ).
須要注意的是 , 使用 Date Facet 時 , 字段名 , 起始時間 , 結束時間 , 時間間隔這 4 個參數都必須提供 .
與 Field Facet 相似 ,Date Facet 也能夠對多個字段進行 Facet. 而且針對每一個字段均可以單獨設置參數 .
2.1 facet.date
該參數表示須要進行 Date Facet 的字段名 , 與 facet.field 同樣 , 該參數能夠被設置屢次 , 表示對多個字段進行 Date Facet.
2.2 facet.date.start
起始時間 , 時間的通常格式爲 」 1995-12-31T23:59:59Z」, 另外可使用 」NOW」,」YEAR」,」MONTH」 等等 , 具體格式能夠參考 org.apache.solr.schema. DateField 的 java doc.
2.3 facet.date.end
結束時間 .
2.4 facet.date.gap
時間間隔 . 若是 start 爲 2009-1-1,end 爲 2010-1-1.gap 設置爲 」+1MONTH」 表示間隔 1 個月 , 那麼將會把這段時間劃分爲 12 個間隔段 . 注意 」+」 由於是特殊字符因此應該用 」%2B」 代替 .
2.5 facet.date.hardend
取值能夠爲 true|false, 默認爲 false. 它表示 gap 迭代到 end 處採用何種處理 . 舉例說明 start 爲 2009-1-1,end 爲 2009-12-25,gap 爲 」+1MONTH」,hardend 爲 false 的話最後一個時間段爲 2009-12-1 至 2010-1-1;hardend 爲 true 的話最後一個時間段爲 2009-12-1 至 2009-12-25.
2.6 facet.date.other
取值範圍爲 before|after|between|none|all, 默認爲 none.
before 會對 start 以前的值作統計 .
after 會對 end 以後的值作統計 .
between 會對 start 至 end 之間全部值作統計 . 若是 hardend 爲 true 的話 , 那麼該值就是各個時間段統計值的和 .
none 表示該項禁用 .
all 表示 before,after,all 都會統計 .
舉例 :
&facet=on &facet.date=date &facet.date.start=2009-1-1T0:0:0Z &facet.date.end=2010-1-1T0:0:0Z &facet.date.gap=%2B1MONTH &facet.date.other=all |
返回結果 :
<lst name="facet_counts"> <lst name="facet_queries"/> <lst name="facet_fields"/> <lst name="facet_dates"> <int name="2009-01-01T00:00:00Z">5</int> <int name="2009-02-01T00:00:00Z">7</int> <int name="2009-03-01T00:00:00Z">4</int> <int name="2009-04-01T00:00:00Z">3</int> <int name="2009-05-01T00:00:00Z">7</int> <int name="2009-06-01T00:00:00Z">3</int> <int name="2009-07-01T00:00:00Z">6</int> <int name="2009-08-01T00:00:00Z">7</int> <int name="2009-09-01T00:00:00Z">2</int> <int name="2009-10-01T00:00:00Z">4</int> <int name="2009-11-01T00:00:00Z">1</int> <int name="2009-12-01T00:00:00Z">5</int> <str name="gap">+1MONTH</str> <date name="end">2010-01-01T00:00:00Z</date> <int name="before">180</int> <int name="after">5</int> <int name="between">54</int> </lst> </lst> |
3. Facet Query
Facet Query 利用相似於 filter query 的語法提供了更爲靈活的 Facet. 經過 facet.query 參數 , 能夠對任意字段進行篩選 .
例 1:
&facet=on &facet.query=date:[2009-1-1T0:0:0Z TO 2009-2-1T0:0:0Z] &facet.query=date:[2009-4-1T0:0:0Z TO 2009-5-1T0:0:0Z] |
返回結果 :
<lst name="facet_counts"> <lst name="facet_queries"> <int name="date:[2009-1-1T0:0:0Z TO 2009-2-1T0:0:0Z]">5</int> <int name="date:[2009-4-1T0:0:0Z TO 2009-5-1T0:0:0Z]">3</int> </lst> <lst name="facet_fields"/> <lst name="facet_dates"/></lst> |
例 2:
&facet=on &facet.query=date:[2009-1-1T0:0:0Z TO 2009-2-1T0:0:0Z] &facet.query=price:[* TO 5000] |
返回結果 :
<lst name="facet_counts"> <lst name="facet_queries"> <int name="date:[2009-1-1T0:0:0Z TO 2009-2-1T0:0:0Z]">5</int> <int name="price:[* TO 5000]">116</int> </lst> <lst name="facet_fields"/> <lst name="facet_dates"/> </lst> |
例 3:
&facet=on &facet.query=cpu:[A TO G] |
返回結果 :
<lst name="facet_counts"> <lst name="facet_queries"> <int name="cpu:[A TO G]">11</int> </lst> <lst name="facet_fields"/> <lst name="facet_dates"/> </lst> |
4. key 操做符
能夠用 key 操做符爲 Facet 字段取一個別名 .
例 :
&facet=on &facet.field={!key=中央處理器}cpu &facet.field={!key=顯卡}videoCard |
返回結果 :
<lst name="facet_counts"> <lst name="facet_queries"/> <lst name="facet_fields"> <lst name="中央處理器"> <int name="Intel 酷睿2雙核 T6600">48</int> <int name="Intel 奔騰雙核 T4300">28</int> <int name="Intel 酷睿2雙核 P8700">18</int> <int name="Intel 酷睿2雙核 T6570">11</int> <int name="Intel 酷睿2雙核 T6670">11</int> <int name="Intel 奔騰雙核 T4400">9</int> <int name="Intel 酷睿2雙核 P7450">9</int> <int name="Intel 酷睿2雙核 T5870">8</int> <int name="Intel 賽揚雙核 T3000">7</int> <int name="Intel 奔騰雙核 SU4100">6</int> <int name="Intel 酷睿2雙核 P8400">6</int> <int name="Intel 酷睿2雙核 SU7300">5</int> <int name="Intel 酷睿 i3 330M">4</int> </lst> <lst name="顯卡"> <int name="ATI Mobility Radeon HD 4">63</int> <int name="NVIDIA GeForce G 105M">24</int> <int name="NVIDIA GeForce GT 240M">21</int> <int name="NVIDIA GeForce G 103M">8</int> <int name="NVIDIA GeForce GT 220M">8</int> <int name="NVIDIA GeForce 9400M G">7</int> <int name="NVIDIA GeForce G 210M">6</int> </lst> </lst> <lst name="facet_dates"/> </lst> |
5. tag 操做符和 ex 操做符
當查詢使用 filter query 的時候 , 若是 filter query 的字段正好是 Facet 字段 , 那麼查詢結果每每被限制在某一個值內 .
例 :
&fq=screenSize:14 &facet=on &facet.field=screenSize |
返回結果 :
<lst name="facet_counts"> <lst name="facet_queries"/> <lst name="facet_fields"> <lst name=" screenSize"> <int name="14.0">107</int> <int name="10.2">0</int> <int name="11.1">0</int> <int name="11.6">0</int> <int name="12.1">0</int> <int name="13.1">0</int> <int name="13.3">0</int> <int name="14.1">0</int> <int name="15.4">0</int> <int name="15.5">0</int> <int name="15.6">0</int> <int name="16.0">0</int> <int name="17.0">0</int> <int name="17.3">0</int> </lst> </lst> <lst name="facet_dates"/> </lst> |
能夠看到 , 屏幕尺寸 (screenSize) 爲 14 寸的產品共有 107 件 , 其它尺寸的產品的數目都是 0, 這是由於在 filter 裏已經限制了 screenSize:14. 這樣 , 查詢結果中 , 除了 screenSize=14 的這一項以外 , 其它項目沒有實際的意義 .
有些時候 , 用戶但願把結果限制在某一範圍內 , 又但願查看該範圍外的概況 . 好比上述狀況 , 既要把查詢結果限制在 14 寸屏的筆記本 , 又想查看一下其它屏幕尺寸的筆記本有多少產品 . 這個時候須要用到 tag 和 ex 操做符 .
tag 就是把一個 filter 標記起來 ,ex(exclude) 是在 Facet 的時候把標記過的 filter 排除在外 .
例 :
&fq={!tag=aa}screenSize:14 &facet=on &facet.field={!ex=aa}screenSize |
返回結果 :
<lst name="facet_counts"> <lst name="facet_queries"/> <lst name="facet_fields"> <lst name=" screenSize"> <int name="14.0">107</int> <int name="14.1">40</int> <int name="13.3">34</int> <int name="15.6">22</int> <int name="15.4">8</int> <int name="11.6">6</int> <int name="12.1">5</int> <int name="16.0">5</int> <int name="15.5">3</int> <int name="17.0">3</int> <int name="17.3">3</int> <int name="10.2">1</int> <int name="11.1">1</int> <int name="13.1">1</int> </lst> </lst> <lst name="facet_dates"/> </lst> |
這樣其它屏幕尺寸的統計信息就有意義了 .
五. SolrJ 對 Facet 的支持
SolrServer server = getSolrServer();//獲取SolrServer SolrQuery query = new SolrQuery();//創建一個新的查詢 query.setQuery("*:*"); query.setFacet(true);//設置facet=on query.addFacetField(new String[] { "cpu", "videoCard" });//設置須要facet的字段 query.setFacetLimit(10);//限制facet返回的數量 QueryResponse response = server.query(query); List<FacetField> facets = response.getFacetFields();//返回的facet列表 for (FacetField facet : facets) { System.out.println(facet.getName()); System.out.println("----------------"); List<Count> counts = facet.getValues(); for (Count count : counts) { System.out.println(count.getName() + ":" + count.getCount()); } System.out.println(); } |