官术网_书友最值得收藏!

第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的方式返回。

主站蜘蛛池模板: 沈阳市| 民勤县| 乡城县| 岳阳县| 威远县| 色达县| 五指山市| 安西县| 巴塘县| 什邡市| 蕉岭县| 武夷山市| 五莲县| 庆云县| 阿鲁科尔沁旗| 岑巩县| 汝城县| 苏尼特左旗| 卢龙县| 贺州市| 永修县| 长子县| 岗巴县| 东至县| 渭南市| 铜陵市| 景洪市| 盘山县| 五台县| 临湘市| 宁远县| 喜德县| 陇南市| 阿拉善右旗| 铅山县| 岑巩县| 贵定县| 香格里拉县| 嘉荫县| 濉溪县| 牡丹江市|