`
donlianli
  • 浏览: 340392 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
Group-logo
Elasticsearch...
浏览量:218551
社区版块
存档分类
最新评论

elasticsearch实现联想输入搜索(like操作)

阅读更多

通常,在项目中需要联想输入(即输入关键字,提示相关词条,类似百度google的搜索)的需求,可能大家都是用的数据库的like '%关键字%‘来实现。但是这样实现有几个问题。

第一、这样的搜索无论是oracle还是mysql,都是无法使用索引的。在oracle中可能有全文检索可以使用,但是个人感觉效果不是很好。

第二、输入的关键字有like的通病,就是只有保含关键字的词条才会被命中。如果中间加个空格之类的,db就无能为力了。

第三、如果要想对命中结果进行相关度排序,这个在常规数据库是无法做到的。虽然,可以按照命中词条的长度进行升序排序,但是加上排序,性能不是很好。

 

下面介绍一下使用elasticsearch实现联想输入的搜索,因为是搜索引擎,天生就不具备上面的3个问题。

在具体介绍使用方法之前,我们先找个搜索数据。我找的是ICD(就是疾病名称的国标),谁让咱一生都在跟他做斗争。这个在网上一搜一堆。

有了数据,我们先要简单描述一下我们要达到的一个目的。一般的搜索都支持汉字 和拼音两种检索方法。我们的这个检索也满足这个需求。

 

搜索需求描述:

1、支持汉字和简拼两种搜索方法。

2、输入“高血压”时,按照相关度,将带“高血压”名称的疾病名称按照相关度降序排序。

3、输入“老年 高血压”,时,将带“老年”和“高血压”名称的疾病名称按照相关度降序排序。

4、输入拼音'gxy‘时,将拼音中带有gxy相关的疾病按照相关度降序排序。

....

 

类似测试用例的需求,到此打住。

 

那么,我们一步一步实现这种需求。

首先,我们定义了一个ICD的类,算作我们的模型,其实没有模型也可以,只要存入到es且知道各个field的名称就行。这个里面我们只需要关注疾病名称diseaseName及简拼pinyin字段即可,这个字段默认是字符串,ES默认会帮我们分词。

import java.io.Serializable;
import java.math.BigDecimal;
/**
 * ICD抽象对象
 * @author donlianli@126.com
 */
public class ICD implements Serializable{
	private static final long serialVersionUID = 6934803011248581109L;
	//疾病ID
	private int id;
	//疾病编码
	private String code;
	//疾病名称
	private String diseaseName;
	//疾病加拼音
	private String mergeName;
	//汉语拼音简拼
	private String pinyin;
	//是否恶心肿瘤
	private boolean isTherioma;
	//是否住院特殊病种
	private boolean isSpecialDisease;
	
	public ICD(BigDecimal id, String diseaseName, String code,
			String pinyin, String isTherioma, String isSpecialDisease) {
		this.id = id.intValue();
		this.diseaseName = diseaseName;
		this.code = code;
		this.pinyin = pinyin;
		if("是".equals(isTherioma)){
			this.isTherioma = true;
		}
		else {
			this.isTherioma = false;
		}
		
		if("是".equals(isSpecialDisease)){
			this.isSpecialDisease = true;
		}
		else {
			this.isSpecialDisease = false;
		}
		this.mergeName = diseaseName + "," + pinyin;
	}
	//set,get ......
	
}

 

 

第二步,将数据存储到elasticsearch里面,我们取个名称叫code,起个type名称叫icd。ICD大概2w条数据,我使用默认的bulkIndex,存到es大概用了3秒。

我这里是把数据从oracle导入到elasticsearch。

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.client.Client;

import com.donlianli.es.ESUtils;
import com.donlianli.es.db.DatabaseUtils;

public class ICDManager {
	
	public static void main(String[] argvs){
		ICDManager manager = new ICDManager();
		manager.indexDataDirect();
	}
	/**
	 * 直接将数据初始化到ES中
	 * 不创建mapping
	 */
	private void indexDataDirect() {
		List<ICD> icdList = getIcdListFromDB();	
		System.out.println(" get icd from db finish,size:" + icdList.size());
		bulkIndex(icdList);
	}
	
	private void bulkIndex(List<ICD> icdList) {
		Client client = ESUtils.getCodeClient();
		BulkRequestBuilder bulkRequest = client.prepareBulk();
		long b = System.currentTimeMillis();
		for(int i=0,l=icdList.size();i<l;i++){
			//业务对象
			ICD icd = icdList.get(i);
			String json = ESUtils.toJson(icd);
			IndexRequestBuilder indexRequest = client.prepareIndex("code","icd")
	        .setSource(json).setId(String.valueOf(icd.getId()));
			//添加到builder中
			bulkRequest.add(indexRequest);
		}
		BulkResponse bulkResponse = bulkRequest.execute().actionGet();
		if (bulkResponse.hasFailures()) {
			System.out.println(bulkResponse.buildFailureMessage());
		}
		long useTime = System.currentTimeMillis()-b;
		System.out.println("useTime:" + useTime);
	}
	private List<ICD> getIcdListFromDB() {
		Connection conn = DatabaseUtils.getOracleConnection();
		String sql = "select * from icd_11";
		PreparedStatement st = null;
		ResultSet rs = null;
		List<ICD> list = new ArrayList<ICD>();
		try{
			st = conn.prepareStatement(sql);
			rs = st.executeQuery();
			while(rs.next()){
				BigDecimal id = rs.getBigDecimal("ID");
				String diseaseName = rs.getString("DISEASE_NAME");
				String code = rs.getString("CODE");
				String pinyin = rs.getString("PINYIN");
				String isTherioma = rs.getString("THERIOMA_FLAG");
				String isSpecialDisease = rs.getString("OTHER_FLAG");
				
				list.add(new ICD(id,diseaseName,code,pinyin,isTherioma,isSpecialDisease));
			}
			
			return list;
		}
		catch(Exception e){
			e.printStackTrace();
		}
		finally{
			try{
			if(rs!= null){
				rs.close();
			}
			if(st!= null){
				st.close();
			}
			conn.close();
			}
			catch(Exception e){
				e.printStackTrace();
			}
		}
		return null;
	}
}

 

 

第三步,搜索接口,跑测试用例。

import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;

import com.donlianli.es.ESUtils;

public class PinyinSearchTest {
	public static void main(String[] args) {
		Client client = ESUtils.getCodeClient();
		String keyWord = "高血压";
//		String keyWord = "老年 高血压";
//		String keyWord = "gxy";
		//多个字段匹配
		MultiMatchQueryBuilder query = QueryBuilders.multiMatchQuery(keyWord, "diseaseName","pinyin");
		
		long b = System.currentTimeMillis();
		SearchResponse response = client.prepareSearch("code").setTypes("icd")
				.setQuery(query)
				.setFrom(0)
				//前20个
				.setSize(20)
				.execute().actionGet();
		long useTime = System.currentTimeMillis()-b;
		System.out.println("search use time:" + useTime + " ms");
		
		SearchHits shs = response.getHits();
		for (SearchHit hit : shs) {
			System.out.println("分数:" 
					+ hit.getScore()
					+ ",ID:"
					+ hit.getId()
					+ ", 疾病名称:"
					+ hit.getSource().get("diseaseName")
					+ ",拼音:" + hit.getSource().get("pinyin"));
		}
		client.close();
	}
}

 

3.1,关键字:'高血压'

search use time:174 ms
分数:2.3859928,ID:6904, 疾病名称:高血压病,拼音:gxyb
分数:2.136423,ID:6907, 疾病名称:高血压I期,拼音:gxyyq
分数:2.12253,ID:6908, 疾病名称:高血压Ⅱ期,拼音:gxyeq
分数:2.12253,ID:6910, 疾病名称:高血压危象,拼音:gxywx
分数:2.0906634,ID:6917, 疾病名称:肾性高血压,拼音:sxgxy
分数:2.0877438,ID:6909, 疾病名称:高血压Ⅲ期,拼音:gxysq
分数:2.0821526,ID:18767, 疾病名称:高原性高血压,拼音:gyxgxy
分数:1.9905697,ID:6906, 疾病名称:恶性高血压,拼音:exgxy
分数:1.9510978,ID:7260, 疾病名称:高血压脑出血,拼音:gxyncx
分数:1.9078629,ID:6923, 疾病名称:肾血管性高血压,拼音:sxgxgxy
分数:1.8312198,ID:6914, 疾病名称:高血压性肾病,拼音:gxyxsb
分数:1.8193114,ID:7367, 疾病名称:高血压性脑病,拼音:gxyxnb
分数:1.8193114,ID:13470, 疾病名称:妊娠引起高血压,拼音:rsyqgxy
分数:1.7919972,ID:6905, 疾病名称:临界性高血压,拼音:ljxgxy
分数:1.7919972,ID:6912, 疾病名称:高血压性心脏病,拼音:gxyxxzb
分数:1.7894946,ID:6928, 疾病名称:继发性高血压,拼音:jfxgxy
分数:1.7062025,ID:6913, 疾病名称:高血压性肾衰竭,拼音:gxyxssj
分数:1.7062025,ID:13485, 疾病名称:孕产妇高血压,拼音:ycfgxy
分数:1.7062025,ID:14534, 疾病名称:新生儿高血压,拼音:xsegxy
分数:1.7062025,ID:16181, 疾病名称:应激性高血压,拼音:yjxgxy

 3.2关键字:'老年 高血压'

search use time:144 ms
分数:1.1089094,ID:6904, 疾病名称:高血压病,拼音:gxyb
分数:0.99291986,ID:6907, 疾病名称:高血压I期,拼音:gxyyq
分数:0.9864628,ID:6908, 疾病名称:高血压Ⅱ期,拼音:gxyeq
分数:0.9864628,ID:6910, 疾病名称:高血压危象,拼音:gxywx
分数:0.9716526,ID:6917, 疾病名称:肾性高血压,拼音:sxgxy
分数:0.97029567,ID:6909, 疾病名称:高血压Ⅲ期,拼音:gxysq
分数:0.96769714,ID:18767, 疾病名称:高原性高血压,拼音:gyxgxy
分数:0.9251333,ID:6906, 疾病名称:恶性高血压,拼音:exgxy
分数:0.9067884,ID:7260, 疾病名称:高血压脑出血,拼音:gxyncx
分数:0.8866946,ID:6923, 疾病名称:肾血管性高血压,拼音:sxgxgxy
分数:0.8510741,ID:6914, 疾病名称:高血压性肾病,拼音:gxyxsb
分数:0.8455395,ID:7367, 疾病名称:高血压性脑病,拼音:gxyxnb
分数:0.8455395,ID:13470, 疾病名称:妊娠引起高血压,拼音:rsyqgxy
分数:0.8328451,ID:6905, 疾病名称:临界性高血压,拼音:ljxgxy
分数:0.8328451,ID:6912, 疾病名称:高血压性心脏病,拼音:gxyxxzb
分数:0.831682,ID:6928, 疾病名称:继发性高血压,拼音:jfxgxy
分数:0.8074301,ID:6820, 疾病名称:老年耳聋,拼音:lnel
分数:0.80348647,ID:7612, 疾病名称:老年痣,拼音:lnz
分数:0.7929714,ID:6913, 疾病名称:高血压性肾衰竭,拼音:gxyxssj
分数:0.7929714,ID:13485, 疾病名称:孕产妇高血压,拼音:ycfgxy

 高血压和老年的相关并都出来了。只可惜老年高血压,没有列入ICD.

3.3拼音:'gxy'

呃?怎么没有出来?

这个问题折腾了我一天。一开始我以为是被es列入了禁用词。后来,找到是因为没有设置analyzer导致,在设analyzer的过程中竟然还犯了好几个低级错误,导致我非常怀疑设置analyzer是否管用。

这个问题涉及到分词,而分词我还没有好好研究过。总之,在创建索引及mapping的时候,指定一个analyzer就可以解决这个问题。

创建index及mapping的代码如下:

import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;

import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.ImmutableSettings.Builder;
import org.elasticsearch.common.xcontent.XContentBuilder;

import com.donlianli.es.ESUtils;
/**
 * 创建code的mapping
 * @author donlianli@126.com
 */
public class CodeMappingTest {
	static final String INDEX_NAME="code";
	static final String TYPE_NAME="icd";
	
	public static void  main(String[] argv) throws Exception{
		Client client = ESUtils.getCodeClient();
		Builder settings = ImmutableSettings.settingsBuilder()
                .loadFromSource(getAnalysisSettings());
		//首先创建索引库
		CreateIndexResponse  indexresponse = client.admin().indices()
		//这个索引库的名称还必须不包含大写字母
		.prepareCreate(INDEX_NAME).setSettings(settings)
		//这里直接添加type的mapping
		.addMapping(TYPE_NAME, getMapping())
		.execute().actionGet();
		
		System.out.println("success:"+indexresponse.isAcknowledged());
	}
	private static String getAnalysisSettings() throws Exception {
		XContentBuilder mapping = jsonBuilder()  
			       .startObject()  
			       //主分片数量
			       .field("number_of_shards",5)
			       .field("number_of_replicas",0)
			         .startObject("analysis")  
			         	.startObject("filter")
			         		//创建分词过滤器
			         		.startObject("pynGram")
			         			.field("type","nGram")
			         			//从1开始
			         			.field("min_gram",1)
			         			.field("max_gram",15)
			         		.endObject()
			         	.endObject()	
			         	
			         	.startObject("analyzer")
			         			//拼音analyszer
			         			.startObject("pyAnalyzer")
			         			.field("type","custom")
			         			.field("tokenizer","standard")
			         			.field("filter", new String[]{ "lowercase","pynGram"})
			         			.endObject()
			         	.endObject()	
			        .endObject()  
			      .endObject();  
		System.out.println(mapping.string());
		return mapping.string();
	}
	/**
	 * mapping 一旦定义,之后就不能修改。
	 * @return
	 * @throws Exception
	 */
	private static XContentBuilder getMapping() throws Exception{
		XContentBuilder mapping = jsonBuilder()  
			       .startObject()  
			         .startObject("icd")  
			         //指定分词器
			         .field("index_analyzer","pyAnalyzer")
			         .startObject("properties")         
			           .startObject("id")
			           		.field("type", "long")
			           		.field("store", "yes")
			           	.endObject()    
			           	
			           .startObject("code")
			           		.field("type", "string")
			           		.field("store", "yes")
			           		.field("index", "analyzed")
			           	.endObject()  
			           	
			           	 .startObject("diseaseName")
			           		.field("type", "string")
			           		.field("store", "yes")
			           		.field("index", "analyzed")
			           	.endObject()  
			           	
			           	 .startObject("mergeName")
			           		.field("type", "string")
			           		.field("store", "yes")
			           		.field("index", "analyzed")
			           	.endObject() 
			           	
			           	.startObject("pinyin")
			           		.field("type", "string")
			           		.field("store", "yes")
			           		.field("index", "analyzed")
			           	.endObject()  
			           	
			           .startObject("isTherioma")
			           		.field("type", "boolean")
			           		.field("store", "yes")
			           .endObject()  
			           
			            .startObject("isSpecialDisease")
			           		.field("type", "boolean")
			           		.field("store", "yes")
			           .endObject()  
			           
			         .endObject()  
			        .endObject()  
			      .endObject();  
		return mapping;
	}
}

 (PS:其实还有一种简单的方法,不用创建analyzer,在搜索的时候,使用'*gxy*'进行搜索也可以)

最后,我还把这个检索跟oracle的like进行了比较。结果发现oracle只用20ms就能算出结果,而es却用了将近100ms。可见这种吹捧的nosql,性能不见得比oracle强大啊,但是毋庸置疑的是,功能确实强大了。

 

 

 更多elasticsearch的内容,请浏览http://www.iteye.com/blogs/subjects/elasticsearch-tutor

对这类话题感兴趣?欢迎发送邮件至donlianli@126.com
关于我:邯郸人,擅长Java,Javascript,Extjs,oracle sql。
更多我之前的文章,可以访问 我的空间

 

0
0
分享到:
评论

相关推荐

    人工智能-项目实践-搜索引擎-基于 ElasticSearch 和 Java 实现的搜索引擎系统,实现关键字高亮搜索、添加文本等

    searchEngine 是基于 ElasticSearch 和 Java 实现的搜索引擎系统,实现关键字高亮搜索、添加文本等功能。 该项目集成了 Spring Boot、ElasticSearch、RestHighLevelClient、Vue.js、Element-ui、Log4j 和 Fastjson ...

    SpringBoot集成Elasticsearch(已实现各种ES操作,上手即可用)

    本实例涵盖ES中的各类操作,如索引操作、CRUD操作、批处理、结果排序、分页查询、检索查询、关键字查询、高亮显示、逻辑查询、过滤查询、分组查询等等。并且已经过生产环境验证,各位可放心使用。

    JAVA实现ElasticSearch的简单实例

    1. **Elasticsearch基础**:Elasticsearch(ES)是一个开源的、分布式全文搜索引擎,它提供了实时数据分析的能力,广泛用于日志分析、监控、搜索应用等领域。其核心特性包括分布式、RESTful接口、实时性、可扩展性和...

    (狂神)ElasticSearch快速入门笔记,ElasticSearch基本操作以及爬虫(Java-ES仿京东实战)

    (狂神)ElasticSearch快速入门笔记,ElasticSearch基本操作以及爬虫(Java-ES仿京东实战),包含了小狂神讲的东西,特别适合新手学习,笔记保存下来可以多看看。好记性不如烂笔头哦~,ElasticSearch,简称es,es是一个...

    es-head Elasticsearch的可视化操作插件

    它提供了一个便捷的操作工具,可以连接Elasticsearch搜索引擎,并提供可视化的操作页面,对Elasticsearch进行各种设置和数据检索功能的管理。 es-head 插件可以在谷歌浏览器中使用,以下是使用方法: 1.下载解压 ...

    基于ElasticSearch的Angularjs联想框功能实现.pdf

    在实现联想框功能时,我们需要使用ElasticSearch的搜索引擎来索引大量数据,然后使用Angularjs的directive结构来实现联想框的展示。通过这种方式,我们可以实现实时快速、稳定、可靠的搜索目的,提高用户体验。 在...

    JAVA使用ElasticSearch查询in和not in的实现方式

    JAVA使用ElasticSearch查询in和not in的实现方式 Elasticsearch是一个基于Lucene的搜索服务器,提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。它是用Java开发的,并作为Apache许可条款下的开放...

    Elasticsearch实现检索词自动补全(检索词补全,自动纠错,拼音补全,繁简转换) 包含demo

    在实际应用中,用户输入的检索词往往可能存在拼写错误、不完整或者使用了同义词等情况,为了提高用户体验和搜索准确性,Elasticsearch 提供了多种功能来支持检索词的自动补全、自动纠错、拼音补全以及繁简转换。...

    基于.netcore搜索封装ElasticSearch.zip

    在.NET Core中使用Elasticsearch,可以借助各种客户端库,实现与Elasticsearch服务器的交互,执行索引、查询、更新和删除等操作。 这个"TCT.Net.Base.ElasticSearch"库很可能是一个封装了Elasticsearch.NET和Nest的...

    SpringBoot集成Elasticsearch(已实现各种ES操作,上手即可用

    本实例涵盖ES中的各类操作,如索引操作、CRUD操作、批处理、结果排序、分页查询、检索查询、关键字查询、高亮显示、逻辑查询、过滤查询、分组查询等等。并且已经过生产环境验证,各位可放心使用。如有不对之处欢迎...

    elasticSearch的操作demo

    最后,`es`包可能是Elasticsearch相关的操作接口或抽象类,它们定义了与Elasticsearch交互的方法,如添加、更新、删除文档,以及查询等。例如: ```java public interface ElasticsearchRepository { void save...

    十分钟学会使用 Elasticsearch 优雅搭建自己的搜索系统.pdf

    而 Elasticsearch将 Lucene 作为其核心来实现所有索引和搜索的功能,通过简单的 RESTful 语法来隐藏掉 Lucene 的复杂性,从而让全文搜索变得简单 ES在Lucene基础上,提供了一些分布式的实现:集群,分片,复制等。 ...

    elasticsearch Java代码实现

    本主题聚焦于“Elasticsearch Java代码实现”,将深入探讨如何使用Java API来执行基本的操作,如创建索引、删除索引、更新索引、模糊搜索以及模糊全文搜索和精确查找。 首先,让我们从`ESManager.java`开始,这个类...

    springBoot动态操作Elasticsearch组件

    通过使用Spring Data Elasticsearch,我们可以方便地实现Elasticsearch的CRUD(创建、读取、更新和删除)操作。 1. **单个的增删改查**: 在Spring Boot应用中,我们可以通过定义Repository接口并继承`...

    es入门操作-elasticsearch入门操作

    "Elasticsearch 入门操作" Elasticsearch 是一个基于 Lucene 库的搜索引擎,提供了一个分布式、支持多用户的全文搜索引擎,具有 HTTP Web 接口和无模式 JSON 文档。所有其他语言可以使用 RESTful API 通过端口 9200...

    Spring Boot elasticsearch7.6.2基础操作:创建索引、新增数据、查询数据

    在本文中,我们将深入探讨...通过这种方式,你可以轻松地在应用中集成Elasticsearch,实现高效的搜索功能。记住,Elasticsearch的强大还在于它的聚合分析、地理位置搜索等高级特性,这些都是进一步提升应用性能的关键。

    基于ElasticSearch的搜索系统的设计与实现

    【基于ElasticSearch的搜索系统的设计与实现】 随着移动互联网时代的到来,网络信息量呈现爆炸式增长,这对信息检索服务提出了更高的要求。传统的搜索引擎在处理大数据量时往往面临搜索效率低下和匹配度单一的问题...

    elasticsearch 8.11.3 windows安装包

    Elasticsearch 是位于 Elastic Stack 核心的分布式搜索和分析引擎。Logstash 和 Beats 有助于收集、聚合和丰富您的数据并将其存储在 Elasticsearch 中。Kibana 使您能够以交互方式探索、可视化和分享对数据的见解,...

    Elastic Search搭建使用教程.pdf(内含ElasticSearch教程权威指南)

    Elasticsearch是一款基于Lucene的开源搜索引擎,它使用RESTful接口进行数据操作,数据以JSON格式存储。Elasticsearch以其高效的全文搜索功能,实时数据处理能力,以及易于使用的特性,在众多企业中得到了广泛应用,...

Global site tag (gtag.js) - Google Analytics