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