書名: Solr權(quán)威指南(下卷)作者名: 蘭小偉本章字?jǐn)?shù): 7748字更新時間: 2019-01-03 15:33:39
第11章 Solr高級查詢
通過第11章,你將可以學(xué)習(xí)到以下內(nèi)容:
?掌握如何使用Function Query以及如何自定義Function Query;
?掌握如何使用Geospatial Query;
?掌握如何使用Pivot Facet和Subfacet;
?掌握如何使用JSON Facet API來實現(xiàn)復(fù)雜的數(shù)據(jù)統(tǒng)計查詢;
?掌握如何使用Solr中的其他查詢組件,比如Elevation(競價排名組件);
?掌握如何使用Solr中的Result Clustering組件實現(xiàn)自動結(jié)果集聚類分組。
Solr作為一個強(qiáng)大的文本搜索平臺,能夠根據(jù)輸入關(guān)鍵字查詢并返回索引文檔,你可能也已經(jīng)了解到了Solr的一些核心功能,比如文本分詞、關(guān)鍵字高亮、結(jié)果集分組等。盡管對于大多數(shù)搜索程序來說,將那些與用戶查詢最佳匹配的索引文檔返回給用戶是非常重要的,但是Solr還有另外一個比較常見的使用場景:聚集結(jié)果集用于數(shù)據(jù)統(tǒng)計分析。Solr的Pivot Facet支持疊加統(tǒng)計多個Facet(維度),它能夠在單個查詢中對任意的聚合分類進(jìn)行計算統(tǒng)計,這使得Solr在提供數(shù)據(jù)分析報告方面變得很有用并且還十分高效。Solr另一個核心功能就是在查詢時能夠?qū)?shù)據(jù)執(zhí)行一個Function(函數(shù))進(jìn)行動態(tài)計算,函數(shù)計算后的結(jié)果可以被用于Filter Query、文檔的相關(guān)性評分、文檔的排序、作為文檔的“偽域”被返回。Solr還提供了強(qiáng)大的Geospatial(地理空間)查詢功能,Geospatial查詢允許你根據(jù)一個點或者一個區(qū)域進(jìn)行多邊形查詢,或以經(jīng)緯度為圓心在指定半徑的圓內(nèi)進(jìn)行查詢,實現(xiàn)附近的位置查詢(比如查詢當(dāng)前用戶所處位置附近的酒店或飯店)。有時候,你期望在返回的索引文檔的域中引用外部數(shù)據(jù)源,Solr提供了這個功能。Solr還支持在同一個Solr實例內(nèi)跨Core在一個外鍵域上執(zhí)行Join操作,這類似于SQL里的兩個表根據(jù)外鍵進(jìn)行多表連接查詢。上述每個復(fù)雜的功能都會在本章中進(jìn)行講解。
11.1 Solr函數(shù)查詢
Solr中的Function Query(函數(shù)查詢)允許你為每個索引文檔執(zhí)行一個函數(shù)進(jìn)行動態(tài)計算值。Function Query是一個比較特殊的查詢,函數(shù)動態(tài)計算后得到的值可以作為一個關(guān)鍵字添加到查詢中,也可以作為文檔的評分,就像是一個普通的關(guān)鍵字查詢同時還能生成相關(guān)性評分。通過使用Function Query,函數(shù)動態(tài)計算值可以被用于修改索引文檔的相關(guān)性評分,以及查詢結(jié)果集排序,而且函數(shù)動態(tài)計算值還可以作為一個“偽域”被動態(tài)添加到每個匹配的索引文檔中并返回給用戶。Function Query還支持嵌套,意思就是一個Function的輸出可以作為另一個Function的輸入,F(xiàn)unction支持任意深度的嵌套。
11.1.1 Function語法
Solr中標(biāo)準(zhǔn)的Function語法是先指定一個Function名稱,后面緊跟著一對小括號,小括號內(nèi)可以傳入零個或多個輸入?yún)?shù),語法使用示例如下:
functionName() functionName(input1) functionName(input1, input2) functionName(input1, input2, ..., inputN)
Function的輸入?yún)?shù)可以是以下任意一種形式:
??一個常量值(數(shù)字或者字符串),語法:
100, 1.45, "hello world"
??一個域名稱,語法:
fieldName, field(fieldName)
??另外一個Function,語法:
functionName(...)
??一個變量,語法:
q={! func}min($f1, $f2)&f1=sqrt(popularity)&f2=1
盡管Solr Function乍一看讓人有點不知所措,其實Solr文檔中定義了每個Function的輸入?yún)?shù)的類型,大部分的Function都遵循Function的標(biāo)準(zhǔn)語法,但是Constant Function(常量函數(shù))、Field Function(域函數(shù))、Parameter Substitution(替換變量)這些屬于特例,它們支持另一種簡單語法。Constant Function(常量函數(shù))的語法就是值本身。Field Function(域函數(shù))的語法就是域的名稱被一個名稱為“field”的函數(shù)包裹。Parameter Substitution(替換變量)的語法就是函數(shù)的輸入變量使用的是一個$開頭的變量,該變量引用自請求URL的查詢文本中定義的變量。除了這3個特例,其他函數(shù)都使用標(biāo)準(zhǔn)的Function語法。
因為Function的所有輸入?yún)?shù)可以被看作是一個Function(函數(shù))(常量值可以被看作常量函數(shù)),所以Function的標(biāo)準(zhǔn)語法在概念上來講,就可以理解為functionName(function1, ..., functionN)。假設(shè)索引文檔中有個fieldContainingNumber域,它其中有個值為-99,那么請思考下面幾個Function的使用示例:
max(2, fieldContainingNumber) //輸出結(jié)果:2 max(fieldContainingNumber, 2) //輸出結(jié)果:2 max(2, -99) //輸出結(jié)果:2 max(-99, 2) //輸出結(jié)果:2 max(2, field(fieldContainingNumber)) //輸出結(jié)果:2 max(field(fieldContainingNumber), add(1,1)) //輸出結(jié)果:2
從上面示例你會注意到,你可以為Constant Function常量函數(shù)(甚至你可以為其他任意標(biāo)準(zhǔn)函數(shù))使用Field Function進(jìn)行包裝,盡管輸入的參數(shù)順序以及每個輸入?yún)?shù)的含義會有所不同,但是它們最終都是用于計算-99和2之間的最大值。將一個函數(shù)的輸入?yún)?shù)看作另外一個函數(shù)的好處就是它允許你用任意的嵌套函數(shù)來實現(xiàn)復(fù)雜計算。并不是所有的Function(函數(shù))都支持同樣類型的輸入?yún)?shù),有些Function(函數(shù))期望接收字符串類型常量參數(shù),而另外一些Function(函數(shù))可能期望接收Integer或者Float類型的數(shù)字。假設(shè)fieldContainingString域的域值為"hallo",請思考下面的函數(shù)調(diào)用示例:
strdist("hello", fieldContainingString, edit) //輸出結(jié)果:0.8 strdist("hello", "hallo", "edit") //輸出結(jié)果:0.8
strdist函數(shù)用于計算兩個字符串之間的相似度,相似度計算是基于一個指定的算法進(jìn)行計算,使用哪種算法是通過函數(shù)的第3個參數(shù)進(jìn)行指定,我們示例中的"edit"表示采用編輯距算法。假如我們將參數(shù)的數(shù)據(jù)類型指定為錯誤的,函數(shù)將會返回什么呢?
strdist("hello", 1000, edit) //輸出結(jié)果:0 strdist(1000, "1000", edit) //輸出結(jié)果:1 strdist("1001", 1000, edit) //輸出結(jié)果:0.75
你可能會覺得函數(shù)會拋出異常,然而實際上函數(shù)內(nèi)部會適當(dāng)?shù)刈詣舆M(jìn)行數(shù)據(jù)類型轉(zhuǎn)換,比如在示例中,將數(shù)字常量1000轉(zhuǎn)換成字符串"1000"。在大多數(shù)情況下,你并不能安全地將一個字符串轉(zhuǎn)換成一個數(shù)字,此時Solr可能會拋出一個異常。因此,需要謹(jǐn)記:函數(shù)嵌套確實很好用,但是并不是所有的函數(shù)都可以隨意嵌套,你需要考慮每個函數(shù)的輸入?yún)?shù)類型是否正確。
Solr的Function可以影響相關(guān)性評分,可以被用于Filter Query過濾結(jié)果集,可以基于函數(shù)計算值進(jìn)行排序,可以將函數(shù)計算值作為索引文檔的“偽域”并返回,甚至可以基于函數(shù)計算值進(jìn)行Facet查詢統(tǒng)計。下一節(jié)我們將深入學(xué)習(xí)這些用法。
11.1.2 使用函數(shù)查詢
為了便于后續(xù)的示例講解,請大家從隨書源碼中獲取Core的相關(guān)配置文件、測試數(shù)據(jù)及導(dǎo)入測試數(shù)據(jù)的測試類,根據(jù)我們前面章節(jié)所學(xué)的知識將本節(jié)測試環(huán)境需要的"news"Core搭建好。
在Solr中執(zhí)行一個典型的關(guān)鍵字查詢,需要在倒排索引中查找關(guān)鍵字,同時計算每個匹配索引文檔的相關(guān)性評分,從而決定哪些索引文檔與查詢關(guān)鍵字比較相關(guān),最后作為結(jié)果集返回。然而查詢不僅能基于搜索關(guān)鍵字,你可以在查詢中插入一個Function并將其看作另外一個搜索關(guān)鍵字。為了演示Function Query,請建立"news" Core并運行隨書源碼中的IndexNews類導(dǎo)入測試數(shù)據(jù)。假如已經(jīng)成功導(dǎo)入了測試數(shù)據(jù),可以執(zhí)行下面的查詢示例:
http://localhost:8080/solr/news/select? q="United States" AND France AND President AND _val_:"recip(ms(date),1,100,100 )"&indent=true
上面的查詢表示查詢包含"United States"短語且包含F(xiàn)rance和President關(guān)鍵字,并且函數(shù)計算值在[1, 100]區(qū)間范圍內(nèi)的索引文檔。這里有3個關(guān)鍵點需要引起你的注意:
??_val_語法:用于注入一個Function Query,這里的_val_可以看作主查詢中的一個查詢Term。
?Function Query并不會改變最終返回結(jié)果集中索引文檔的總數(shù)。
??查詢的最后相關(guān)性評分一般是查詢中每個Term的相關(guān)性評分的總和,"United States"、France和President這些Term的相關(guān)性評分是基于tf-idf相似度算法進(jìn)行計算的,但是Function Query的評分計算是函數(shù)自身的計算值。
基于上述3點,你可以了解到示例中的Function Query是為了給新添加的索引文檔進(jìn)行加權(quán)。最新的索引文檔的相關(guān)性評分可能是100,而最舊的索引文檔的評分可能是1,剩下的索引文檔的評分會落在[1, 100]之間。注意,每個索引文檔的最后評分是標(biāo)準(zhǔn)化的,這意味著每個索引文檔的最后評分不會都到達(dá)100分,最近添加的索引文檔相比之前顯示會更靠前。
Function在Solr中無處不在,它可以對用戶的q參數(shù)進(jìn)行加權(quán),它還可以在不同的Query Parser中使用,比如在eDisMax Query Parser中通過bf參數(shù)指定Function;它還可以作為Filter Query的一部分,用于索引文檔排序等。但是最重要的是你需要了解Function Query是如何被執(zhí)行的。前面的示例中你已經(jīng)見過了"_val_"這樣的語法,你可能還記得我們之前介紹過的Function Query Parser,可以通過一個本地參數(shù)!func來構(gòu)造一個Function Query,比如:{! func}functionName(…)。Function Query本質(zhì)就是將函數(shù)計算值作為構(gòu)造的函數(shù)查詢的評分,因此,以下幾種查詢語法是等價的:
q=solr AND _val_:"add(1, boostField)" q=solr AND _query_:"{! func}add(1, boostField)" q=solr AND {! func v="add(1, boostField)"}
為一個查詢,添加一個Function看起來非常有用,它能修改查詢匹配的索引文檔的評分。如果你期望過濾掉函數(shù)計算值不在指定范圍內(nèi)的索引文檔,可以使用Function Range Query Parser來解決函數(shù)計算值范圍過濾。
如果你需要根據(jù)函數(shù)計算值的范圍來過濾索引文檔,那么Function Range Query Parser(簡稱frange)會比較適用你的使用場景,F(xiàn)range過濾器通過執(zhí)行一個指定的Function Query,過濾掉函數(shù)計算值不在指定范圍內(nèi)的索引文檔。為了演示這種功能,我們搭建測試環(huán)境。這里會用到隨書源碼中的"salestax" Core, Core相關(guān)配置文件和測試數(shù)據(jù)以及導(dǎo)入數(shù)據(jù)測試類請從相應(yīng)章節(jié)中查找獲取。導(dǎo)入完成之后,請看下面這個查詢示例:
http://localhost:8080/solr/salestax/select? q=*:*& fq={! frange l=10 u=15}product(basePrice, sum(1, $userSalesTax))& userSalesTax=0.07
以上查詢先通過sum函數(shù)計算$userSalesTax和1的價格之和,然后將basePrice域的域值與sum函數(shù)計算返回值通過product函數(shù)求乘積,最后通過frange過濾器的l(即lower表示最小值)和u(即upper表示最大值)參數(shù)定義了product函數(shù)計算返回值的取值范圍,符合這個區(qū)間范圍限制的索引文檔將會被返回。你還可以設(shè)置incll(即include lower)和inclu(include upper)參數(shù)來指定是否包含兩個邊界值。
你可能會說,能不能自定義一個Function來靈活地過濾任意查詢匹配的結(jié)果集?關(guān)于自定義Function相關(guān)內(nèi)容會在本章的后續(xù)章節(jié)講解。現(xiàn)在你已經(jīng)知道如何為查詢添加Function,并且理解了函數(shù)評分是如何計算的,接下來讓我們繼續(xù)學(xué)習(xí)使用函數(shù)動態(tài)計算值來代替靜態(tài)的域值。
11.1.3 將函數(shù)計算值作為“偽域”返回
在上一節(jié),你已經(jīng)了解到函數(shù)的輸入?yún)?shù)可以被看作是一個函數(shù),既然如此,那么似乎我們可以使用Function來替換Field,因為Field和Function最終都是返回一個值。事實也是如此,你不僅可以為每個索引文檔動態(tài)計算得到一個數(shù)值,還可以將這個數(shù)值作為一個“偽域”隨索引文檔一起返回。重新回到我們在上一節(jié)中的"salestax"示例,執(zhí)行下面這個查詢:
http://localhost:8080/solr/salestax/select? q=*:*& userSalesTax=0.07& fl=id, basePrice, product(basePrice, sum(1, $userSalesTax))
上面這個查詢返回的結(jié)果會是怎樣的呢?正如你看到的那樣,你會發(fā)現(xiàn)返回的索引文檔中多了一個“偽域”, “偽域”的域名稱就是我們定義的函數(shù)表達(dá)式,“偽域”的域值就是函數(shù)表達(dá)式最終的計算值。之所以稱為“偽域”,是因為它并不真正存在于我們的索引數(shù)據(jù)中,但是它仍然會像其他存儲域一樣被一起返回。“偽域”的名稱使用函數(shù)表達(dá)式可能會顯得冗長難看,不過值得慶幸的是,Solr提供了為“偽域”定義任意你想要的別名的功能,具體如何為“偽域”定義別名,請看下面這個示例:
http://localhost:8080/solr/salestax/select? q=*:*& userSalesTax=0.07& fl=id, basePrice, totalPrice:product(basePrice, sum(1, $userSalesTax))
這里我們?yōu)?product(basePrice, sum(1, $userSalesTax))"這個偽域定義了一個別名totalPrice,最終返回結(jié)果里偽域名稱就是我們這里定義的別名了。正因為你可以為“偽域”定義任意的別名,因此也就意味著可以將“偽域”的別名定義為索引文檔中真實存在的域的域名稱,這樣就可以直接使用“偽域”的值來冒充該域的真實域值。當(dāng)你期望根據(jù)用戶權(quán)限來控制某些用戶沒有權(quán)限訪問某個域的真實域值的時候,通過“偽域”別名來冒充真實域會對你很有用。
通過使用Function,你可以在域的域值返回之前對其進(jìn)行任意操縱,比如經(jīng)過函數(shù)計算變換它的值。你不僅可以通過函數(shù)修改文檔中任何域的域值,還可以通過函數(shù)修改文檔的相關(guān)性評分,從而影響文檔是否應(yīng)該被返回,或者文檔在返回的結(jié)果集中的排序。
11.1.4 根據(jù)函數(shù)進(jìn)行排序
在上一節(jié),你了解了如何將一個函數(shù)的動態(tài)計算值添加到索引文檔中作為一個“偽域”在查詢結(jié)果集中返回;你也知道了如何根據(jù)函數(shù)計算值來對查詢結(jié)果集進(jìn)行過濾,以及如何使用函數(shù)來修改匹配文檔的相關(guān)性評分。接下來,讓我們繼續(xù)學(xué)習(xí)如何基于函數(shù)動態(tài)計算值來對查詢結(jié)果集進(jìn)行排序。根據(jù)函數(shù)動態(tài)計算值來對查詢結(jié)果集進(jìn)行排序的語法與普通查詢中根據(jù)某個域排序的語法沒什么太大的不同,具體請看下面這個查詢示例:
http://localhost:8080/solr/salestax/select? q=*:*& userSalesTax=0.07& sort=product(basePrice, sum(1, $userSalesTax)) asc, score desc
上面這個查詢,根據(jù)product函數(shù)計算值升序進(jìn)行排序,然后再按文檔的評分降序排序,你可以結(jié)合其他函數(shù)構(gòu)造更為復(fù)雜的Function Query,比如:
http://localhost:8080/solr/salestax/select? q=_query_:"{! func}recip(ms(date),1,100,100)"& userSalesTax=0.07& totalPriceFunc=product(basePrice, sum(1, $userSalesTax))& fq={! frange l=10 u=15 v=$totalPriceFunc}& fl=*, totalPrice:$totalPriceFunc& sort=$totalPriceFunc asc, score desc
上面這個查詢首先根據(jù)Function Query Parser對"{! func}recip(ms(date),1,100,100)"查詢表達(dá)式進(jìn)行解析,構(gòu)造成Function Query;然后通過_query_語法將Function Query轉(zhuǎn)換成普通的Query,轉(zhuǎn)換后的查詢并沒有過濾任何索引文檔,它只是用來根據(jù)文檔的date域的時間遠(yuǎn)近對索引文檔進(jìn)行加權(quán);然后通過fq對$totalPriceFunc變量表示的函數(shù)最終計算值進(jìn)行區(qū)間范圍過濾,不在[10, 15]區(qū)間內(nèi)的索引文檔將會被過濾掉,通過sort參數(shù)先按照$totalPriceFunc變量表示的函數(shù)計算值進(jìn)行升序排序;再按照索引文檔的評分降序排序;最后通過fl里將函數(shù)計算值當(dāng)作索引文檔的“偽域”一并返回。這個示例綜合使用了我們前面所講解的知識點。
11.1.5 Solr中的內(nèi)置函數(shù)
到目前為止,你已經(jīng)知道如何在Solr中應(yīng)用Function。由于Solr內(nèi)置的函數(shù)非常多,而且還在不斷增加中,所以本書這部分內(nèi)容不可能面面俱到,如果本書有遺漏某個函數(shù)沒有提及,讀者可以自行查閱資料學(xué)習(xí)。但是我會盡量覆蓋Solr中內(nèi)置的大部分常用函數(shù),并詳細(xì)解釋每個函數(shù)的用途以及使用語法。Solr中的內(nèi)置函數(shù)大致分為5類:data transformation(數(shù)據(jù)轉(zhuǎn)換)、Math(數(shù)學(xué)計算)、Relevancy(計算相關(guān)性評分)、Distance(距離計算)、Boolean(布爾操作)。
1.?dāng)?shù)據(jù)轉(zhuǎn)換類函數(shù)
Solr中比較常用的函數(shù)大都是轉(zhuǎn)換類函數(shù),即將數(shù)據(jù)通過一個或多個函數(shù)計算從一個值轉(zhuǎn)換成另一個值。下面會詳細(xì)介紹每個轉(zhuǎn)換類函數(shù)的用途和用法(如表11-1所示)。
表11-1 數(shù)據(jù)轉(zhuǎn)換類函數(shù)表

2.?dāng)?shù)學(xué)函數(shù)
數(shù)學(xué)計算是比較常用的數(shù)據(jù)分析操作。Solr全面支持?jǐn)?shù)學(xué)計算,支持包括加減乘除以及三角函數(shù)等多種數(shù)學(xué)函數(shù)。表11-2列舉了Solr中支持的數(shù)學(xué)函數(shù)。
表11-2 Solr中支持的數(shù)學(xué)函數(shù)

3.相關(guān)性評分函數(shù)
Solr的相關(guān)性評分默認(rèn)是基于DefaultSimilarity類進(jìn)行計算而來的,DefaultSimilarity利用索引的統(tǒng)計信息來決定哪些索引文檔與查詢匹配。這些相關(guān)性評分是針對每個索引文檔進(jìn)行計算得到一個綜合的評分,你還可以使用相關(guān)性評分函數(shù)對個別查詢進(jìn)行部分評分(比如你只想返回Term的出現(xiàn)頻率信息)。相關(guān)性評分的所有核心統(tǒng)計都包含于相關(guān)性評分函數(shù)中,比如tf-idf。表11-3列舉了Solr中支持的相關(guān)性評分函數(shù)。
表11-3 Solr中支持的相關(guān)性評分函數(shù)

可以使用上面表中的相關(guān)性函數(shù)來重寫評分計算,請思考下面這個Solr查詢示例:
http://localhost:8080/solr/salestax/select? fq={! cache=false}text:"microsoft office"& q={! func}sum( product( tf(text, "microsoft"), idf(text, "microsoft") ), product( tf(text, "office"), idf(text, "office") ) )
上面這個查詢首先分別計算"microsoft"和"office"的tf和idf,然后分別計算tf和idf的乘積,最后返回兩個乘積的和作為文檔的最后評分。然后根據(jù)短語"microsoft office"進(jìn)行過濾,將不符合fq參數(shù)要求的索引文檔過濾掉。通過這些相關(guān)性評分函數(shù),Solr為你打開了評分模型,可以隨心所欲地在查詢時通過相關(guān)性評分函數(shù)來干預(yù)文檔評分。
4.距離計算函數(shù)
有時候你希望能夠計算兩個值之間的距離,比如你要計算地球上兩個點(甚至兩個向量)之間的空間距離,這在地理空間搜索中會比較有用。你還可以計算兩個字符串之間的相似度,表11-4列舉了Solr中支持的距離計算函數(shù)。
表11-4 Solr中支持的距離計算函數(shù)

從以上的表你可以了解到,Solr全方位支持距離計算函數(shù)。dish函數(shù)允許你指定0-norm、1-norm、2-norm、無窮norm用于N維空間內(nèi)兩個點或向量的距離計算,比如計算二維空間內(nèi)兩點的歐幾里得距離:dist(2, x1, y1, x2, y2),計算三維空間內(nèi)兩點的曼哈頓距離:dist(1, x1, y1, z1, x2, y2, z2)。
sqedist函數(shù)相比歐幾里得函數(shù)計算執(zhí)行開銷更小,它返回的是歐幾里得距離的平方根,對于二維空間里的坐標(biāo)點,平方歐式距離計算的是勾股定理(a2+ b2= c2)中的c2,因為歐式距離計算必須要額外的對c2進(jìn)行開平方根從而得到確切的c值,如果你只需要在文檔排序或者文檔相關(guān)性評分時獲取到文檔的相對順序,而不關(guān)心兩點之間的實際準(zhǔn)確距離值,那么使用sqedist函數(shù)計算性能會更高效。
hsin函數(shù)用于計算球面上兩點的距離。radiusInKm參數(shù)表示球面的半徑,如果你想計算地球上兩點的距離,地球的近似半徑值是6371.01(赤道半徑),由于地球并不是一個完美的球體,因此這個近似半徑值的精確度還可以再提升0.5%。如果指定的是經(jīng)緯度,那么isDegrees需要設(shè)置為true,如果坐標(biāo)點指定的是弧度,那么isDegrees參數(shù)設(shè)置為false。x1、y1表示第一個坐標(biāo)點,x2、y2表示第二個坐標(biāo)點。
ghhsin函數(shù)與hsin函數(shù)類似,但是ghhsin函數(shù)接收的不是角度和弧度,而是geohash值。geohash函數(shù)可以接收經(jīng)度和未讀值,并將它們進(jìn)行g(shù)eohash編碼,得到的編碼字符串可以作為ghhsin函數(shù)的輸入?yún)?shù),如果在索引中你的某個域存儲的是geohash函數(shù)編碼后的字符串,那么可能會用到這些函數(shù)。
strdist函數(shù)用于計算兩個字符串之間的相似度,一般用于相似Term的模糊匹配。如果將一個字符串看作一個多字符的向量,strdist函數(shù)計算的是兩個字符串(字符向量)之間的距離,最終計算得到的相似度值范圍是[0, 1],0表示一點也不相似,1表示兩個字符串完全相等。strdist函數(shù)的s1、s2參數(shù)表示輸入的兩個字符串,distType參數(shù)用于指定距離計算的算法,如果distType參數(shù)值為ngram,默認(rèn)使用2個字符串相比較,但是可以通過ngram參數(shù)覆蓋。
geodist函數(shù)用于計算地球上兩點之間的空間距離。sfield參數(shù)必須是LatLonType域,函數(shù)返回的距離單位是千米,lon參數(shù)表示經(jīng)度,lat表示緯度,pt參數(shù)即point的縮寫,即坐標(biāo)點(x, y)。geodist函數(shù)內(nèi)部使用hsin函數(shù),簡化了使用語法。geodist函數(shù)是Solr中最常用的距離計算函數(shù),我們會在下一章節(jié)更詳細(xì)的講解它在Geospatial Search(地理空間查詢)中的應(yīng)用。
5.布爾函數(shù)
Boolean操作不僅可以用于關(guān)鍵字查詢,它還可以用于構(gòu)建或連接任意復(fù)雜的Function Query(函數(shù)查詢),通過if、and、or、not、xor、exists等函數(shù),可以檢查域值或其他函數(shù)計算以及基于這些檢查有條件的返回域值,表11-5列舉了Solr支持的布爾函數(shù)。
表11-5 Solr支持的布爾函數(shù)

Solr提供了這么多豐富的函數(shù)供我們選擇,應(yīng)該能夠滿足大部分用戶的需求,與Solr中的其他功能一樣,你也可以自定義Function來擴(kuò)展Solr的Function,接下來將學(xué)習(xí)如何創(chuàng)建我們自己的Function。
11.1.6 自定義函數(shù)
有時候,你可能想要執(zhí)行某些數(shù)據(jù)操作,但Solr內(nèi)置函數(shù)并不支持。值得慶幸的是,在Solr中,可以很簡單地實現(xiàn)自己的自定義函數(shù)??梢詮募夹g(shù)上進(jìn)行內(nèi)存計算做任何事情,比如訪問外部文件或數(shù)據(jù)源獲取數(shù)據(jù),甚至運行任意你想要的代碼。實現(xiàn)自定義的Function唯一的約束就是你能容忍函數(shù)計算完成耗費多長時間。因為自定義的Function代碼可能會針對每個索引文檔進(jìn)行計算,它需要在合理的響應(yīng)時間內(nèi)快速的作出響應(yīng)。
在本節(jié),我們將演示如何創(chuàng)建一個自定義Function來將多個域的域值拼接成一個字符串。為了能夠在Solr中以插件的方式使用我們的自定義Function,你需要完成以下3個步驟:
1)編寫一個類表示你的Function,這個類需要繼承ValueSource類,它能夠為索引中的每個索引文檔返回一個計算值。
2)編寫一個ValueSourceParser類,它能夠解析自定義Function的語法并將其解析為變量傳遞給第一步自定義的ValueSource類。
3)在solrconfig.xml中注冊你在第二步中定義的ValueSourceParser類,指定它的完整類路徑以及函數(shù)名稱,但自定義Function執(zhí)行時,會使用你定義的函數(shù)名稱,并且自定義的ValueSourceParser類解析輸入?yún)?shù)并傳遞給第一步自定義的ValueSource類。
我們將要實現(xiàn)的concatenation函數(shù)需要繼承Solr中的ValueSource類,并重寫其getValues方法,最后返回一個FunctionValues對象,F(xiàn)unctionValues對象可以為索引中每個索引文檔返回計算值,以下的代碼演示了如何創(chuàng)建一個ConcatenateFunction類:
/** * Created by Lanxiaowei * 自定義Concatenate函數(shù) */ public class ConcatenateFunction extends ValueSource { protected final ValueSource valueSource1; protected final ValueSource valueSource2; protected final String delimiter; public ConcatenateFunction(ValueSource valueSource1, ValueSource valueSource2, String delimiter) { if (valueSource1 == null || valueSource2 == null){ throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "One or more inputs missing for concatenate function" ); } this.valueSource1 = valueSource1; this.valueSource2 = valueSource2; if (delimiter ! = null){ this.delimiter = delimiter; } else{ this.delimiter = ""; } } public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException { final FunctionValues firstValues = valueSource1.getValues( context, readerContext); final FunctionValues secondValues = valueSource2.getValues( context, readerContext); return new StrDocValues(this) { @Override public String strVal(int doc) { return firstValues.strVal(doc) .concat(delimiter) .concat(secondValues.strVal(doc)); } @Override public String toString(int doc) { StringBuilder sb = new StringBuilder(); sb.append("concatenate("); sb.append("\"" + firstValues.toString(doc) + "\"") .append(', ') .append("\"" + secondValues.toString(doc) + "\"") .append(', ') .append("\"" + delimiter + "\""); sb.append(')'); return sb.toString(); } }; } @Override public boolean equals(Object o) { if (this.getClass() ! = o.getClass()) return false; ConcatenateFunction other = (ConcatenateFunction) o; return this.valueSource1.equals(other.valueSource1) && this.valueSource2.equals(other.valueSource2) && this.delimiter == other.delimiter; } @Override public int hashCode() { long combinedHashes; combinedHashes = (this.valueSource1.hashCode() + this.valueSource2.hashCode() + this.delimiter.hashCode()); return (int) (combinedHashes ^ (combinedHashes >>> 32)); } @Override public String description() { return "Concatenates two values together with an optional delimiter"; } }
關(guān)于ConcatenateFunction類有兩個關(guān)鍵點:輸入?yún)?shù)和getValues方法的返回值。Concatenate Function類的輸入?yún)?shù)由兩個ValueSource對象表示,ConcatenateFunction類會將兩個ValueSource對象包含的值使用連接符c進(jìn)行拼接?;仡櫸覀冎爸v解的知識,一個Function的輸入?yún)?shù)可以是另一個函數(shù)的返回值,通過定義兩個ValueSource對象而不是定義兩個String字符串,你的函數(shù)可以接收任意輸入。盡管將輸入?yún)?shù)統(tǒng)統(tǒng)采用ValueSource類型來定義帶來了很大的靈活性,但ConcatenateFunction類的構(gòu)造函數(shù)的第3個參數(shù)delimiter是一個String類型,它也可以定義為ValueSource類型,它還可以是從其他域或者其他函數(shù)計算后返回的值,在我們這里,我們假設(shè)我們的delimiter參數(shù)是顯式的在請求中傳遞的。為了能夠理解ConcatenateFunction類的輸出,你需要查看getValues方法,這個方法返回一個FunctionValues對象,并且getValues方法必須返回FunctionValues類型,因為我們的concatenation函數(shù)的返回值是一個字符串,我們在內(nèi)部使用StrDocValues類表包含這個返回值。StrDocValues類是FunctionValues類的一個實現(xiàn)類,它能夠?qū)nteger、Boolean等類型數(shù)據(jù)返回成一個String。FunctionValues類有很多子類實現(xiàn),有些實現(xiàn)可能會使用到特定的緩存,因此如果你需要這方面的優(yōu)化,那么你需要檢出Solr的源碼進(jìn)行驗證確認(rèn)。StrDocValues對象內(nèi)部包含了一個strVal(docid)方法,當(dāng)Function被執(zhí)行時,它會針對每個索引文檔調(diào)用一次,正因為如此,所以對于一些執(zhí)行開銷比較大的復(fù)雜查詢,你需要確保strVal方法能夠盡快執(zhí)行。
現(xiàn)在你已經(jīng)知道了Function是如何計算并返回計算值的,下一步就是理解請求參數(shù)是如何傳入ConcatenateFunction對象的。以下代碼演示了如何解析輸入?yún)?shù)并傳入我們的自定義Function:
public class ConcatenateFunctionParser extends ValueSourceParser { public ValueSource parse(FunctionQParser parser) throws SyntaxError { ValueSource value1 = parser.parseValueSource(); ValueSource value2 = parser.parseValueSource(); String delimiter = null; if (parser.hasMoreArguments()){ delimiter = parser.parseArg(); } return new ConcatenateFunction(value1, value2, delimiter); } }
以上代碼演示了如何使用FunctionQParser對象將輸入?yún)?shù)進(jìn)行解析并傳入我們自定義的函數(shù)中。FunctionQParser按照標(biāo)準(zhǔn)的函數(shù)語法進(jìn)行解析,比如functionName(input1, input2, …),根據(jù)請求的函數(shù)名稱查找合適的ValueSourceParser實現(xiàn)類??梢酝ㄟ^調(diào)用FunctionQParser內(nèi)部的parseValueSource()、parseArg()、parseFloat()等方法來獲取傳遞給我們Function的輸入?yún)?shù)。在ConcatenateFunctionParser示例中,我們期望獲取兩個ValueSource對象(可以是一個域,可以是用戶輸入的任意字符串或者其他函數(shù)的返回值)以及一個delimiter字符串參數(shù),在從請求中讀取到這些輸入?yún)?shù)之后,我們創(chuàng)建了一個ConcatenateFunction對象并傳入輸入?yún)?shù)到其構(gòu)造函數(shù)中。
實現(xiàn)自定義concatenation函數(shù)需要把創(chuàng)建的類都完成,剩下就是在solrconfig.xml中的<<config>>元素下進(jìn)行注冊,讓Solr知道我們自定義的新函數(shù)。
<valueSourceParser name="concat" class="sia.ch15.ConcatenateFunctionParser" />
上面的name屬性表示注冊的函數(shù)名稱,函數(shù)名稱怎么定義完全由我們決定。class表示我們自定義的FunctionParser實現(xiàn)類完整包路徑。下面是我們自定義的concat函數(shù)的幾個使用示例:
concat("hello", "world", "-") //返回"hello-world" concat("hello", "world", ", ") //返回"hello, world" concat(123,456, ".") //返回"123.456" concat("no", "delimiter") //返回"nodelimiter" concat("hello", "world", "field1") //返回"hellofield1world"
如果想要在查詢中使用我們剛剛自定義的concat函數(shù),那么可以這樣使用:
http://localhost:8080/solr/yourcore/select? q=*:*& fl=res:concat(concat(field1, field2, ", "), "! ")
上面的查詢中我們演示了如何使用concat函數(shù),先將field1和field2這兩個域的域值使用逗號連接起來,然后將拼接后的值繼續(xù)與感嘆號!進(jìn)行拼接。最終concat函數(shù)返回值作為“偽域”以別名res的方式返回。
- Python Deep Learning
- Practical Windows Forensics
- MySQL數(shù)據(jù)庫管理與開發(fā)(慕課版)
- PySide GUI Application Development(Second Edition)
- C語言程序設(shè)計學(xué)習(xí)指導(dǎo)與習(xí)題解答
- Building an RPG with Unity 2018
- 深入淺出Serverless:技術(shù)原理與應(yīng)用實踐
- Raspberry Pi Home Automation with Arduino(Second Edition)
- Android開發(fā)三劍客:UML、模式與測試
- 智能手機(jī)APP UI設(shè)計與應(yīng)用任務(wù)教程
- Visual C++從入門到精通(第2版)
- jQuery從入門到精通(微課精編版)
- Spark for Data Science
- HTML 5與CSS 3權(quán)威指南(第4版·上冊)
- Python for Secret Agents