Solr的Facet學習筆記與個人總結

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();
}


 

 

 

 

 

發佈了49 篇原創文章 · 獲贊 25 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章