Solr Facet 查詢
Solr Facet查詢
一)概述
Facet是solr的高階搜尋功能之一,可以給使用者提供更友好的搜尋體驗.在搜尋關鍵字的同時,能夠按照Facet的欄位進行分組並統計。例如下圖所示,你上淘寶,輸入“電腦”進行搜尋,就會出現品牌分類,價格範圍等分類,這個就叫Facet。
二)Solr Facet型別
Solr提供了4種類型的Fact
<lst name="facet_counts"> <lst name="facet_queries"/> <lst name="facet_fields"/> <lst name="facet_dates"/> <lst name="facet_ranges"/> </lst>
- facet_queries:代表自定義條件查詢facet,類似資料庫的count函式
- facet_fields :代表根據欄位分組查詢,類似資料庫的group by count的組合
- facet_dates :根據日期區間分組查詢
- facet_ranges:當然了,日期有區間,數字也有,這個就是根據數字分組查詢
三)Solr Facet元件
Solr的預設requestHandler已經包含了Facet元件(solr.FacetComponent).如果自定義requestHandler或者對預設的requestHandler自定義元件列表,那麼需要將Facet加入到元件列表中去.
四)facet query
Facet Query 使用者自定義條件查詢facet,他提供了非常靈活的Facet.通過facet.query引數,可以對任意欄位進行篩選.下面通過例項來闡述。基本上他的用法,都會在我例項中體現出來
例一:日期區間查詢
&facet=true
&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=true
&facet.query=brand:聯想 AND price:1100
返回結果
"facet_counts":{
"facet_queries":{
"brand:聯想 AND price:1100":1},
"facet_fields":{},
"facet_dates":{},
"facet_ranges":{}}}
五)Field Facet
Facet欄位通過在請求中加入facet.field引數加以宣告,如果需要對多個欄位進行Facet查詢,那麼將該引數宣告多次.這就是類似於資料庫的group by 加上count的功能,非常的靈活。
例項一:最簡單的field facet
&facet=true
&facet.field=brand
&facet.field=price
返回結果如下
"facet_counts":{
"facet_queries":{},
"facet_fields":{
"brand":[
"蘋果",4,
"聯想",3,
"惠普",2],
"price":[
"1100.0",2,
"2200.0",2,
"3300.0",2,
"1200.0",1,
"2100.0",1,
"4400.0",1]},
"facet_dates":{},
"facet_ranges":{}}}
從返回結果可以看出各個field欄位互不影響;而且可以針對,下面例項會體現
每個Facet欄位設定查詢引數.以下介紹的引數既可以應用於所有的Facet欄位,也可以應用於每個單獨的Facet欄位.應用於單獨的欄位時通過下面語法實現
f.欄位名.引數名=引數值
例如:將facet.prefix引數應用於brand欄位,可以採用如下形式
&facet.field=brand
&facet.field=price
&f.brand.facet.prefix=聯
返回結果如下:
"facet_counts":{
"facet_queries":{},
"facet_fields":{
"brand":[
"聯想",3],
"price":[
"1100.0",2,
"2200.0",2,
"3300.0",2,
"1200.0",1,
"2100.0",1,
"4400.0",1]},
"facet_dates":{},
"facet_ranges":{}}}
溫馨提示:上面的facet.prefix就是一個引數名,這個很容易誤解為兩個,因為他中間有個點
上面介紹了facet.field引數,下面介紹field fact的其他引數
1).facet.prefix
表示Facet欄位值的字首.比如facet.field=cpu&facet.prefix=Intel,那麼對cpu欄位進行Facet查詢,返回的cpu都是以“Intel”開頭的。
2).facet.sort
表示Facet欄位值以哪種順序返回.可接受的值為true(count)|false(index,lex). true(count)表示按照count降序; false(index,lex)表示按照欄位值升序(字母,數字的順序)排列.預設情況下為true(count).當facet.limit值為負數時,預設facet.sort= false(index,lex).
3).facet.limit
限制Facet欄位返回的結果條數.預設值為100.如果此值為負數,表示不限制.
4).facet.offset
返回結果集的偏移量,預設為0.它與facet.limit配合使用可以達到分頁的效果.
5).facet.mincount
限制了Facet欄位值的最小count,預設為0.合理設定該引數可以將使用者的關注點集中在少數比較熱門的領域.相當於group by having
6).facet.missing
預設為””,如果設定為true或者on,那麼將統計那些該Facet欄位值為null的記錄.
7).facet.method
取值為enum或fc,預設為fc.該欄位表示了兩種Facet的演算法,與執行效率相關.
enum適用於欄位值比較少的情況,比如欄位型別為布林型,或者欄位表示中國的所有省份.Solr會遍歷該欄位的所有取值,並從filterCache裡為每個值分配一個filter(這裡要求solrconfig.xml裡對filterCache的設定足夠大).然後計算每個filter與主查詢的交集.
fc(表示Field Cache)適用於欄位取值比較多,但在每個文件裡出現次數比較少的情況.Solr會遍歷所有的文件,在每個文件內搜尋Cache內的值,如果找到就將Cache內該值的count加1.
8).facet.enum.cache.minDf
當facet.method=enum時,此引數其作用,minDf表示minimum document frequency.也就是文件內出現某個關鍵字的最少次數.該引數預設值為0.設定該引數可以減少filterCache的記憶體消耗,但會增加總的查詢時間(計算交集的時間增加了).如果設定該值的話,官方文件建議優先嚐試25-50內的值.
六) Date Facet
日期型別的欄位在文件中很常見,如商品上市時間,貨物出倉時間,書籍上架時間等等.某些情況下需要針對這些欄位進行Facet.不過時間欄位的取值有無限性,使用者往往關心的不是某個時間點而是某個時間段內的查詢統計結果. Solr為日期欄位提供了更為方便的查詢統計方式.當然,欄位的型別必須是DateField(或其子型別)。
需要注意的是,使用Date Facet時,欄位名,起始時間,結束時間,時間間隔這4個引數都必須提供.與Field Facet類似,Date Facet也可以對多個欄位進行Facet.並且針對每個欄位都可以單獨設定引數。
簡單例項參考
&facet.date=birthday
&facet.date.start=2014-01-00T09:15:00Z
&facet.date.end=2014-12-00T09:15:00Z
&facet.date.gap=%2B1MONTH
返回結果如下所示
"facet_counts":{
"facet_queries":{},
"facet_fields":{},
"facet_dates":{
"birthday":{
"2013-12-31T09:15:00Z":0,
"2014-01-31T09:15:00Z":0,
"2014-02-28T09:15:00Z":0,
"2014-03-28T09:15:00Z":0,
"2014-04-28T09:15:00Z":0,
"2014-05-28T09:15:00Z":0,
"2014-06-28T09:15:00Z":0,
"2014-07-28T09:15:00Z":0,
"2014-08-28T09:15:00Z":0,
"2014-09-28T09:15:00Z":1,
"2014-10-28T09:15:00Z":5,
"2014-11-28T09:15:00Z":3,
"gap":"+1MONTH",
"start":"2013-12-31T09:15:00Z",
"end":"2014-12-28T09:15:00Z"}},
"facet_ranges":{}}}
Date Facet引數說明
1).facet.date
該引數表示需要進行Date Facet的欄位名,與facet.field一樣,該引數可以被設定多次,表示對多個欄位進行Date Facet.
2).facet.date.start
起始時間,時間格式為1995-12-31T23:59:59Z
3).facet.date.end
結束時間.
4).facet.date.gap
時間間隔.如果start為2009-1-1,end為2010-1-1.gap設定為+1MONTH表示間隔1個月,那麼將會把這段時間劃分為12個間隔段.
注意+因為是特殊字元所以應該用%2B代替.
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.
6).facet.date.other
取值範圍為before|after|between|none|all,預設為none,before會對start之前的值做統計,after會對end之後的值做統計,between會對start至end之間所有值做統計.如果hardend為true的話,那麼該值就是各個時間段統計值的和.none表示該項禁用.all表示before,after,all都會統計.
例項參考,演示fact.date.other、跟單獨對某個欄位起作用
&facet.date=birthday
&facet.date.start=2014-01-00T09:15:00Z
&facet.date.end=2014-12-00T09:15:00Z
&facet.date.gap=%2B1MONTH
&facet.date.other=all
&f.birthday.facet.mincount=3 --單獨對某個欄位起作用,把統計值小於3的過濾掉
返回結果如下:
"facet_counts":{
"facet_queries":{},
"facet_fields":{},
"facet_dates":{
"birthday":{
"2014-10-28T09:15:00Z":5,
"2014-11-28T09:15:00Z":3,
"gap":"+1MONTH",
"start":"2013-12-31T09:15:00Z",
"end":"2014-12-28T09:15:00Z",
"before":0,
"after":0,
"between":9}},
"facet_ranges":{}}}
七)Facet Range
範圍統計分組統計,跟Date Facet一樣,只是他們定位的欄位的型別不同,Data Fact是做日期的分組統計的,而Fact Range是做數字分組統計的,在次強調,是做數字分組統計的,對於字串,日期是不可以的。
引數跟上面的Date Facet基本一致,如下,就不做解釋了,參考Date Facet的各個引數
1. facet.range
2. facet.range.start
3. facet.range.end
4. facet.range.gap
5. facet.range.hardend
6. facet.range.other
7. facet.range.include
參考例項
&facet.range=price
&facet.range.start=1000
&facet.range.end=5000
&facet.range.gap=1000
&f.price.facet.mincount=2--單獨對某個欄位起作用,把統計值小於2的過濾掉
返回結果如下:
"facet_counts":{ "facet_queries":{}, "facet_fields":{}, "facet_dates":{}, "facet_ranges":{ "price":{ "counts":[ "1000.0",3, "2000.0",3, "3000.0",2], "gap":1000.0, "start":1000.0, "end":5000.0}}}}
八)key 操作符
上面已經介紹了facet的四類統計,下面介紹一下key,什麼是key?
答:key操作符可以為Facet欄位取一個別名。哦原來如此簡單!
參考例項:
引數
&facet=true
&facet.query=brand:聯想 AND price:1100
返回結果
"facet_counts":{
"facet_queries":{
"brand:聯想 AND price:1100":1},
"facet_fields":{},
"facet_dates":{},
"facet_ranges":{}}}
--------------------------------
引數
&facet=true
&facet.query={!key=聯想}brand:聯想 AND price:1100
返回結果
"facet_counts":{
"facet_queries":{
"聯想":1},
"facet_fields":{},
"facet_dates":{},
"facet_ranges":{}}}
從上面可以看出來,這樣可以讓欄位名統一起來,方便我們拿到請求資料後,封裝成自己的物件
九)tag操作符和ex操作符
這個也非常的重要,看下應用場景,當查詢使用filter query 或者q的時候,如果filter query的欄位正好是Facet欄位,那麼查詢結果往往被限制在某一個值內.
參考例項
&fq=price:[1000 TO 2000]
&facet.field=price
返回結果
"facet_counts":{
"facet_queries":{},
"facet_fields":{
"price":[
"1100.0",2,
"1200.0",1,
"2100.0",0,
"2200.0",0,
"3300.0",0,
"4400.0",0]},
"facet_dates":{},
"facet_ranges":{}}}
從返回的結果可以看到fq將查詢的結果集限制在了price 在1000 至 2000之間,其他範圍的統計沒有實際意義。
有些時候,使用者希望把結果限制在某一個範圍以內,又希望檢視該範圍外的概況,像上述情況,使用者想把結果限制在(price)1000~2000之間,但是又想檢視其他價格區間有多少產品。這個時候需要用到tag和ex操作符.tag就是把一個filter標記起來,ex(exclude)是在Facet的時候把標記過的filter排除在外.
參考例項
&fq={!tag=aa}price:[1000 TO 2000]
&facet.field={!ex=aa}price
返回結果
"facet_counts":{
"facet_queries":{},
"facet_fields":{
"price":[
"1100.0",2,
"2200.0",2,
"3300.0",2,
"1200.0",1,
"2100.0",1,
"4400.0",1]},
"facet_dates":{},
"facet_ranges":{}}}
這樣其它價格區間的統計資訊就有意義了.
十)Facet 欄位設計
一、Facet欄位的要求
Facet的欄位必須被索引.一般來說該欄位無需分詞,無需儲存.
無需分詞是因為該欄位的值代表了一個整體概念,如電腦的品牌”聯想”代表了一個整體概念,如果拆成”聯”,”想”兩個字都不具有實際意義.另外該欄位的值無需進行大小寫轉換等處理,保持其原貌即可.
無需儲存是因為一般而言使用者所關心的並不是該欄位的具體值,而是作為對查詢結果進行分組的一種手段,使用者一般會沿著這個分組進一步深入搜尋.
二、特殊情況
對於一般查詢而言,分詞和儲存都是必要的.比如CPU型別“Intel 酷睿2雙核 P7570”,拆分成“Intel”,“酷睿”,“P7570”這樣一些關鍵字並分別索引,可能提供更好的搜尋體驗.但是如果將CPU作為Facet欄位,最好不進行分詞.這樣就造成了矛盾,解決方法為,將CPU欄位設定為不分詞不儲存,然後建立另外一個欄位為它的COPY,對這個COPY的欄位進行分詞和儲存.
<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"/>
官網API: http://wiki.apache.org/solr/SimpleFacetParameters