基本狀況就是,媒體、試題、分類,媒體可能有多個試題,一個試題可能有多個分類,分類爲三級分類加上一個綜合屬性。經過試題名稱、分類等搜索查詢媒體。php
如今的問題爲,搜索結果不精確,部分搜索無結果,ES的數據結構不知足搜索需求。解決方案就是,重構ES數據結構,採用父子關係的方式,創建media和question兩個type。html
全程使用https://github.com/mobz/elasticsearch-head,這個進行ES的管理和查看,很方便。git
從ES的說明能夠看出,ES是面向文檔,其實全部的數據都是一張張卡片,例以下面這個:github
幾個重要的概念:mapping,index,type能夠直接參考上圖:
_index,能夠看作是數據庫吧,上圖命名爲links,在對搜索進行操做的時候須要指定,就像指定數據庫同樣。web
_type,約等於表,例如這裏的media,上圖的_type列你們還能夠看到有question數值。其實就等於咱們的media表和question表數據庫
mapping,映射、繪製...的地圖,顧名思義。其實就是表結構和表關係。例如上面點開的卡片內,_source內有id,language等,其實就是mapping。mapping還包括關係的定義,例如這裏的media是parent父級,question的結構建立的時候就須要指定_parent爲media。json
瞭解了以上幾個概念,咱們就能夠進行結構建立了。就像數據庫同樣,咱們須要一個media表,放媒體信息,媒體ID做爲惟一的ID。而後question表,放question的信息(這裏還包括試題的分類),咱們把同一個試題分配爲不一樣分類也算做不一樣試題。這裏這樣的結構,也是爲了根據多級分類搜索的時候方便而設置的,下面說搜索的時候會挑明。數據結構
這是初始化建立index和mapping的代碼:app
$elasticaClient = new \Elastica\Client(array('host'=>'localhost','port'=>9200)); // Load index $elasticaIndex = $elasticaClient->getIndex('links'); // Create the index new // 建立index的參數自行參見官網,就不一一解釋了 $elasticaIndex->create( array( 'number_of_shards' => 4, 'number_of_replicas' => 1, 'analysis' => array( 'analyzer' => array( 'indexAnalyzer' => array( 'type' => 'custom', 'tokenizer' => 'standard', 'filter' => array('lowercase', 'mySnowball') ), 'searchAnalyzer' => array( 'type' => 'custom', 'tokenizer' => 'standard', 'filter' => array('standard', 'lowercase', 'mySnowball') ) ), 'filter' => array( 'mySnowball' => array( 'type' => 'snowball', 'language' => 'German' ) ) ) ), true ); //建立media的mapping,做爲父級 $mediaType = $elasticaIndex->getType('media'); // Define mapping $mapping = new \Elastica\Type\Mapping(); $mapping->setType($mediaType); $mapping->setParam('index_analyzer', 'indexAnalyzer'); $mapping->setParam('search_analyzer', 'searchAnalyzer'); // Define boost field $mapping->setParam('_boost', array('name' => '_boost', 'null_value' => 1.0)); // Set mapping // 定義media的字段和屬性 $mapping->setProperties(array( 'id' => array('type' => 'string', 'include_in_all' => FALSE), 'media_name' => array('type' => 'string', 'include_in_all' => TRUE), 'tstamp' => array('type' => 'date', 'include_in_all' => FALSE), 'language' => array('type' => 'integer', 'include_in_all' => FALSE), '_boost' => array('type' => 'float', 'include_in_all' => FALSE) )); // Send mapping to type // 保存media的mapping $mapping->send(); //建立question的mapping,父級爲media $questionType = $elasticaIndex->getType('question'); // Define mapping $mapping = new \Elastica\Type\Mapping(); $mapping->setType($questionType); $mapping->setParam('index_analyzer', 'indexAnalyzer'); $mapping->setParam('search_analyzer', 'searchAnalyzer'); // Define boost field $mapping->setParam('_boost', array('name' => '_boost', 'null_value' => 1.0)); // Set mapping // question的字段和屬性 $mapping->setProperties(array( 'id' => array('type' => 'string', 'include_in_all' => FALSE), 'level_one' => array('type' => 'integer', 'include_in_all' => FALSE), 'level_two' => array('type' => 'integer', 'include_in_all' => FALSE), 'level_thr' => array('type' => 'integer', 'include_in_all' => FALSE), 'top_level' => array('type' => 'string', 'include_in_all' => FALSE), 'cat_id' => array('type' => 'integer', 'include_in_all' => FALSE), 'quest_hash' => array('type' => 'string', 'include_in_all' => TRUE), 'content' => array('type' => 'string', 'include_in_all' => TRUE), 'view_num' => array('type' => 'integer', 'include_in_all' => FALSE), 'like_num' => array('type' => 'integer', 'include_in_all' => FALSE), '_boost' => array('type' => 'float', 'include_in_all' => FALSE) )); $mapping->setParent("media");//指定question的父類 // Send mapping to type // 保存question的mapping $mapping->send();
上面雖然是PHP代碼,可是最終生成的也是一個url請求。elasticsearch
下面說搜索,搜索的話ES是經過query、filter等來處理的,query裏面有不少不一樣的方式,參見:http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-queries.html,filter也是,參見http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-filters.html
這裏搜索是這樣的,根據media的media_name作query_string搜索,而後對media進行has_child的filter搜索,has_child搜索內使用boolAnd的filter來篩選。
下面是搜索的代碼:
$query = new \Elastica\Query(); if (!empty($input['key'])) { //針對media的media_name字段設置QueryString查詢 $elasticaQueryString = new \Elastica\Query\QueryString(); $elasticaQueryString->setFields(array("media.media_name")); $elasticaQueryString->setQuery($input['key']); // $query->setQuery($elasticaQueryString); }else { $query->setQuery(new MatchAll()); //命中所有紀錄 } $language_bool = false; $elasticaFilterAnd = new \Elastica\Filter\BoolAnd(); //language也是針對media,設置BoolAnd查詢 if (isset($input['language']) && !empty($input['language'])) { $filterl1 = new \Elastica\Filter\Term(); $filterl1->setTerm('language', intval($input['language'])); $elasticaFilterAnd->addFilter($filterl1); $language_bool = true; } // //對子集進行篩選查詢,使用has_child $subFilterAnd = new \Elastica\Filter\BoolAnd(); $bool = false; // 一級分類條件 if (isset($input['level_one']) && !empty($input['level_one'])) { $filterl1 = new \Elastica\Filter\Term(); $filterl1->setTerm('level_one', intval($input['level_one'])); $subFilterAnd->addFilter($filterl1); $bool = true; } // 二級分類條件 if (isset($input['level_two']) && !empty($input['level_two'])) { $filterl1 = new \Elastica\Filter\Term(); $filterl1->setTerm('level_two', intval($input['level_two'])); $subFilterAnd->addFilter($filterl1); $bool = true; } // 三級分類條件 if (isset($input['level_thr']) && !empty($input['level_thr'])) { $filterl1 = new \Elastica\Filter\Term(); $filterl1->setTerm('level_thr', intval($input['level_thr'])); $subFilterAnd->addFilter($filterl1); $bool = true; } // 直接指定分類ID查詢 if (isset($input['cat_id']) && !empty($input['cat_id'])) { $filterl1 = new \Elastica\Filter\Term(); $filterl1->setTerm('cat_id', intval($input['cat_id'])); $subFilterAnd->addFilter($filterl1); $bool = true; } // 分類屬性查詢 if (isset($input['top_level']) && !empty($input['top_level'])) { $filterl1 = new \Elastica\Filter\Term(); $filterl1->setTerm('top_level', $input['top_level']); $subFilterAnd->addFilter($filterl1); $bool = true; } if($bool){ // 聲明一個查詢,用於放入子查詢 $subQuery = new \Elastica\Query(); // 使用filteredquery,融合query和filter $filteredQuery = new \Elastica\Query\Filtered(new MatchAll(),$subFilterAnd); // 添加filterquery到子查詢 $subQuery->setQuery($filteredQuery); // 聲明hasChildFilter,聲明的時候就指定子查詢的內容,指定查詢的子表(也就是TYPE)爲question $filterHasChild = new \Elastica\Filter\HasChild($subQuery,"question"); // 將擁有子類查詢增長到父級查詢的filter中 $elasticaFilterAnd->addFilter($filterHasChild); } if($bool || $language_bool){ // 將filter增長到父查詢匯中 $query->setFilter($elasticaFilterAnd); } // // $query->setFrom($start); // Where to start? $query->setLimit($limit); // How many? // //Search on the index. $elasticaResultSet = $elasticaIndex->search($query);
上面看上去很長的PHP代碼,其實最後發出的時候也只是一個發送json數據的請求,對照下面這個json數據和上面的代碼,你們就很容易明白了:
{ "query": { "query_string": { "query": "like", "fields": [ "media.media_name" ] } }, "filter": { "and": [ { "term": { "language": 1 } }, { "has_child": { "query": { "filtered": { "query": { "match_all": {} }, "filter": { "and": [ { "term": { "top_level": "111" } } ] } } }, "type": "question" } } ] }, "from": 0, "size": 20 }
總結:ES很強大,不單單是在導入性能仍是搜索性能,或者是搜索結果,或者是結構的調整上來講。做爲剛接觸不久的也能很快的進行數據結構重構並重寫搜索,仍是算比較好的。惟一的缺點就是,中文的文檔太少,須要不斷的使用谷歌來查看文檔、去官網看文檔說明、看PHP的API。
在處理過程當中,感謝以前的哥們留下的代碼,至少不是摸瞎。感謝同事搞的谷歌搜索,通常人我不告訴他,你們能夠去試試 另客網,首頁的搜索框裏面,選擇谷歌。
部分借鑑來自於這裏:http://www.spacevatican.org/2012/6/3/fun-with-elasticsearch-s-children-and-nested-documents/
我的筆記,寫來你們分享分享,確定有不足錯誤的,歡迎你們指出,謝謝。