solr facet查詢及solrj 讀取facet資料(相當有用)
Facet 是 solr 的高階搜尋功能之一 , 可以給使用者提供更友好的搜尋體驗 . 在搜尋關鍵字的同時 , 能夠按照 Facet 的欄位進行分組並統計 .
一般代表了實體的某種公共屬性 , 如商品的分類 , 商品的製造廠家 , 書籍的出版商等等 .
Facet 的欄位必須被索引 . 一般來說該欄位無需分詞 , 無需儲存 .
無需分詞是因為該欄位的值代表了一個整體概念 , 如電腦的品牌 ” 聯想 ” 代表了一個整體概念 , 如果拆成 ” 聯 ”,” 想 ” 兩個字都不具有實際意義 . 另外該欄位的值無需進行大小寫轉換等處理 , 保持其原貌即可 .
無需儲存是因為一般而言使用者所關心的並不是該欄位的具體值 , 而是作為對查詢結果進行分組的一種手段 , 使用者一般會沿著這個分組進一步深入搜尋 .
對於一般查詢而言 , 分詞和儲存都是必要的 . 比如 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"/> |
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=on” 或者 ”facet=true” 只有這樣 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 |
表示 Facet 欄位值的字首 . 比如 ”facet.field=cpu&facet.prefix=Intel”, 那麼對 cpu欄位進行 Facet 查詢 , 返回的 cpu 都是以 ”Intel” 開頭的 ,”AMD” 開頭的 cpu 型號將不會被統計在內 .
表示 Facet 欄位值以哪種順序返回 . 可接受的值為 true(count)|false(index,lex). true(count) 表示按照 count 值從大到小排列 . false(index,lex) 表示按照欄位值的自然順序 (字母 , 數字的順序 ) 排列 . 預設情況下為 true(count). 當 facet.limit 值為負數時 ,預設 facet.sort= false(index,lex).
限制 Facet 欄位返回的結果條數 . 預設值為 100. 如果此值為負數 , 表示不限制 .
返回結果集的偏移量 , 預設為 0. 它與 facet.limit 配合使用可以達到分頁的效果 .
限制了 Facet 欄位值的最小 count, 預設為 0. 合理設定該引數可以將使用者的關注點集中在少數比較熱門的領域 .
預設為 ””, 如果設定為 true 或者 on, 那麼將統計那些該 Facet 欄位值為 null 的記錄.
取值為 enum 或 fc, 預設為 fc. 該欄位表示了兩種 Facet 的演算法 , 與執行效率相關 .
enum 適用於欄位值比較少的情況 , 比如欄位型別為布林型 , 或者欄位表示中國的所有省份.Solr 會遍歷該欄位的所有取值 , 並從 filterCache 裡為每個值分配一個 filter( 這裡要求 solrconfig.xml 裡對 filterCache 的設定足夠大 ). 然後計算每個 filter 與主查詢的交集 .
fc( 表示 Field Cache) 適用於欄位取值比較多 , 但在每個文件裡出現次數比較少的情況 .Solr 會遍歷所有的文件 , 在每個文件內搜尋 Cache 內的值 , 如果找到就將 Cache 內該值的count 加 1.
當 facet.method=enum 時 , 此引數其作用 ,minDf 表示 minimum document frequency. 也就是文件內出現某個關鍵字的最少次數 . 該引數預設值為 0. 設定該引數可以減少 filterCache 的記憶體消耗 , 但會增加總的查詢時間 ( 計算交集的時間增加了 ). 如果設定該值的話 ,官方文件建議優先嚐試 25-50 內的值 .
日期型別的欄位在文件中很常見 , 如商品上市時間 , 貨物出倉時間 , 書籍上架時間等等 . 某些情況下需要針對這些欄位進行 Facet. 不過時間欄位的取值有無限性 , 使用者往往關心的不是某個時間點而是某個時間段內的查詢統計結果 . Solr 為日期欄位提供了更為方便的查詢統計方式 .當然 , 欄位的型別必須是 DateField( 或其子型別 ).
需要注意的是 , 使用 Date Facet 時 , 欄位名 , 起始時間 , 結束時間 , 時間間隔這 4 個引數都必須提供 .
與 Field Facet 類似 ,Date Facet 也可以對多個欄位進行 Facet. 並且針對每個欄位都可以單獨設定引數 .
該引數表示需要進行 Date Facet 的欄位名 , 與 facet.field 一樣 , 該引數可以被設定多次 , 表示對多個欄位進行 Date Facet.
起始時間 , 時間的一般格式為 ” 1995-12-31T23:59:59Z”, 另外可以使用 ”NOW”,”YEAR”,”MONTH” 等等 , 具體格式可以參考 org.apache.solr.schema. DateField 的 java doc.
結束時間 .
時間間隔 . 如果 start 為 2009-1-1,end 為 2010-1-1.gap 設定為 ”+1MONTH” 表示間隔1 個月 , 那麼將會把這段時間劃分為 12 個間隔段 . 注意 ”+” 因為是特殊字元所以應該用 ”%2B” 代替 .
取值可以為 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.
取值範圍為 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> |
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> |
這樣其它螢幕尺寸的統計資訊就有意義了 .
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(); } |