salesforce零基础学习(七十五)浅谈SOSL(Salesforce Object Search Language)

在工作中,我们更多操作的是一个表的对象,所以我们对SOQL的使用很多。但是有时候,我们需要对几个表进行查询操作,类似salesforce的全局搜索功能,这时,使用SOQL没法满足功能了,我们就需要使用SOSL.其实不只是多个表检索可以使用SOSL,如果针对某个字段进行高级的检索也可以使用SOSL。

背景:Account表中的Name字段,存储了以下的数据 1.上海电信 2.上海-电信 3.上海(电信) 4.电信-上海 5.电信上海 6.电信(上海) 7.上海xx电信 8.海上信电 9.海信电上。

当用户搜索上海电信时,需要将1,2,3,4,5,6,7检索出来,8,9排除。使用正常的SOQL语句实现起来难度较大,这种情况可以考虑使用SOSL,尽管SOSL不一定将所有的结果返回,但是可以返回大部分情况。

一.SOSL简单介绍

SOSL全称为Salesforce Object Search Language。SOSL查询可以在以下环境使用: Search()的调用/apex语句/Visualforce的Controller和getter方法里面/Eclipse的Schema Explorer(没有测试成功)

SOSL支持对多个objects同时查询text/email/phone类型字段的数据,SOSL可以查询标准的对象以及自定义的对象。当然SOSL不是所有的对象或者字段都支持搜索,以下情况下是不允许搜索的:

1.sObject不允许搜索:创建sObject或者自带标准sObject,只有允许搜索的sObject才可以使用SOSL,判断一个sObject是否可以搜索,可以使用Schema的DescribeSObjectResult类来判断,如果希望一个自定义对象允许搜索,只需要把allow search勾选即可。

2.Number, date, or checkbox 这几种类型是不支持使用SOSL的,如果需要搜索这几种,需要使用SOQL。

3.Textarea 类型,除非SearchGroup选择的是ALL FIELDS,否则不支持搜索。

4.关联到对象上的Attachment数据不允许搜索。

除了上述的使用限制以外,其实SOSL还有一些其他的限制,比如SOSL语句长度不能超过20000个字符,超过的话会报error。其他的限制详看开发文档。

SOSL在apex中调用时,search query使用的是单引号'',在search调用中使用的是{},下面的demo以及代码均以apex写法为主。

二.SOSL的语法

SOSL的语法如下:

1.FIND:搜索指定的文本,如果searchQuery超过10000,则无结果返回,如果超过4000,所有的逻辑都将移除。SearchQuery除了纯文本以外,还可以使用*或者?的通配符进行匹配,*代表后面的所有位为任意内容,?代表后面的一位为任意内容。         比如FIND 'z*o'会将所有zero,zoo数据查询出来,但是'z?o'会过滤掉zero对应的数据,只会查出zoo对应的数据。         searchQuery也可以使用与或等操作,详情查看SOQL与SOSL开发文档。

2.IN:设置查询组--即查询的类型,SearchGroup包含四种固定的类型:ALL FIELDS/EMAIL FIELDS/NAME FIELDS/PHONE FIELDS。如果想要在Name或者Email/Phone类型中进行搜索,则可以设置指定的类型,否则设置ALL FIELDS,默认查询组为ALL FIELDS。

3.RETURNING:此部分作为搜索返回结果的处理部分,显得尤为重要,RETURNING可以返回一个对象,也可以返回多个对象,多个对象通过逗号分隔;对象中可以返回多个字段,也可以在返回的结果中添加自定义的逻辑。比如我们希望搜索Opportunity和Account的Name中包含zero中的数据,其中,要求Opportunity中的数据按照创建日期正序排列,只查询十条,并且只搜索Name和StageName字段,Account要求Name除了含有zero以外还需要包含zhang,并且最多只查询1条,这种情况下RETURNING就可以发挥神奇的作用了。         eg:FIND 'zero' IN ALL FIELDS RETURNING account(where Name like '%zhang%' limit 1),Opportunity(Name,StageName order by createddate asc limit 10) 备注(如果使用order by,object的field不能为空,如果上述内容修改成以下写法便是错误的)         FIND 'zero' IN ALL FIELDS RETURNING account(where Name like '%zhang%' limit 1),Opportunity(order by createddate asc limit 10)

其他部分自行查看开发文档。

三.SOSL应用

封装了一个SOSL工具类,用户可以根据需要查询的关键字,设置返回的结果的格式来返回需要的数据,如果不设置returning的field的内容,则默认返回所有可以访问的字段,否则返回指定字段:

  1 public with sharing class SOSLController {
  2 
  3     public class RetrieveWrapper {
  4         //keyword:used to retrieve this
  5         public String retrieveKeyword{get;set;}
  6         //search group: values :(ALL FIELDS/EMAIL FIELDS/NAME FIELDS/PHONE FIELDS/SIDEBAR FIELDS)
  7         public String searchGroup{get;set;}
  8         //obj api name -> field    eg: account->[Name,Site]
  9         public Map<String,List<String>> objName2FieldsMap{get;set;}
 10         //obj api name -> condition eg : account -> where name like 'test%' order by name asc limit 10 offset 1
 11         public Map<String,String> objName2QueryConditionMap{get;set;}
 12     }
 13 
 14     public class SearchResultWrapper {
 15         //sobject api name  eg:Account
 16         public String objName{get;set;}
 17         //sObject Field Name => Value 
 18         public Map<String,Object> objFieldName2Value{get;set;}
 19     }
 20 
 21     public static List<SearchResultWrapper> search(RetrieveWrapper wrapper) {
 22         String retrieveSQL = buildRetrieveSQL(wrapper);
 23         List<SearchResultWrapper> searchResultWrappers = new List<SearchResultWrapper>();
 24         if(retrieveSQL == null || (!retrieveSQL.contains('FIND'))) {
 25             return null;
 26         }
 27         System.debug(LoggingLevel.INFO, '*** retrieveSQL: ' + retrieveSQL);
 28         List<List<sObject>> searchResults = search.query(retrieveSQL);
 29         if(searchResults.size() > 0) {
 30             for(List<sObject> objs : searchResults) {
 31                 String objName;
 32                 if(objs.size() > 0) {
 33                     String objId = objs.get(0).Id;
 34                     objName = getAPINameByObjId(objId);
 35                 }
 36                 List<String> retrieveFields;
 37                 if(objName != null) {
 38 
 39                     if(wrapper.objName2FieldsMap != null && wrapper.objName2FieldsMap.get(objName) != null) {
 40                         retrieveFields = wrapper.objName2FieldsMap.get(objName);
 41                     } else {
 42                         retrieveFields = getAvailableFields(objName);
 43                     }
 44                 }
 45 
 46                 for(sObject obj : objs) {
 47                     SearchResultWrapper  resultWrapper = new SearchResultWrapper();
 48                     resultWrapper.objName = objName;
 49                     Map<String,Object> fieldValueMap = new Map<String,Object>();
 50                     for(String field : retrieveFields){
 51                         if(obj.get(field) != null) {
 52                             fieldValueMap.put(field, obj.get(field));
 53                         }
 54                     }
 55                     resultWrapper.objFieldName2Value = fieldValueMap;
 56                     searchResultWrappers.add(resultWrapper);
 57                 }
 58             }
 59         }
 60         return searchResultWrappers;
 61     }
 62 
 63     private static String buildRetrieveSQL(RetrieveWrapper wrapper) {
 64         String fetchSQL;
 65         if(wrapper.retrieveKeyword != null && wrapper.retrieveKeyword.trim() != '') {
 66             String keyword = '\'' + wrapper.retrieveKeyword + '\'';
 67             fetchSQL = 'FIND ' + keyword;
 68 
 69             if(wrapper.searchGroup != null) {
 70                 fetchSQL += ' IN ' + wrapper.searchGroup;
 71             } else {
 72                 fetchSQL += ' IN ALL FIELDS';
 73             }
 74 
 75             if(wrapper.objName2FieldsMap != null) {
 76                 List<String> objToFieldsList = new List<String>();
 77                 for(String key : wrapper.objName2FieldsMap.keySet()) {
 78                     String objName = key;
 79                     String fieldStr;
 80                     if(wrapper.objName2FieldsMap != null && wrapper.objName2FieldsMap.get(objName) != null) {
 81                         fieldStr = String.join(wrapper.objName2FieldsMap.get(objName),',');
 82                     } else {
 83                         fieldStr = String.join(getAvailableFields(objName), ',');
 84                     }
 85                     String filterStr;
 86                     if(wrapper.objName2QueryConditionMap != null) {
 87                         filterStr = wrapper.objName2QueryConditionMap.get(objName);
 88                     }
 89 
 90                     if(String.isNotEmpty(filterStr)){
 91                         fieldStr = '(' + fieldStr + ' WHERE ' + filterStr +')';
 92                     }
 93                     else{
 94                         if(fieldStr != null) {
 95                             fieldStr = '(' + fieldStr +')';
 96                         }
 97                     }
 98                     if(fieldStr != null) {
 99                         objName = key + fieldStr;
100                     } else {
101                         objName = key;
102                     }
103                     
104                     objToFieldsList.add(objName);
105                 }
106 
107                 if(objToFieldsList.size() > 0) {
108                     fetchSQL += ' RETURNING ' + String.join(objToFieldsList, ',');
109                 }
110             }
111         }
112         return fetchSQL;
113     }
114 
115     private static String getAPINameByObjId(String objId) {
116         String objPrefix = objId.left(3);
117         return objId2APIName.get(objPrefix);
118     }
119 
120 
121     private static Map<String,String> objId2APIName {
122         get {
123             if(objId2APIName == null) {
124                 objId2APIName = new Map<String,String>();
125                 Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe(); 
126                 for(String objectName : gd.keySet()) {
127                     Schema.SObjectType objectType = gd.get(objectName);
128                     String prefix = objectType.getDescribe().getKeyPrefix();
129                     if(prefix != null) {
130                         objId2APIName.put(prefix, objectName);
131                     }
132                 }
133             }
134             return objId2APIName;
135         }
136         set;
137     }
138 
139     private static List<String> getAvailableFields(String objName) {
140         List<String> availableFields = new List<String>();
141         List<Schema.DescribeSObjectResult> objDescribes = Schema.describeSObjects(new List<String>{objName});
142         Schema.DescribeSObjectResult objDescribe;
143         if(objDescribes != null && objDescribes.size() > 0) {
144             objDescribe = objDescribes.get(0);
145         } else {
146             return null;
147         }
148         Map<String,SObjectField> sObjectFieldMaps = objDescribe.fields.getMap();
149         for(String objField : sObjectFieldMaps.keySet()) {
150             SObjectField field = sObjectFieldMaps.get(objField);
151             DescribeFieldResult fieldResult = field.getDescribe();
152             if(fieldResult.isAccessible()) {
153                 availableFields.add(fieldResult.getName());
154             }
155         }
156         return availableFields;
157     }
158 }

调用代码如下:

SOSLController.RetrieveWrapper wrapper = new SOSLController.RetrieveWrapper();
wrapper.retrieveKeyword = '上海电信';
wrapper.objName2FieldsMap = new Map<String,List<String>>();
List<String> userFieldsList = new List<String>();
userFieldsList.add('Name');
wrapper.objName2FieldsMap.put('account',userFieldsList);
//wrapper.objName2FieldsMap.put('account',null);
List<SOSLController.SearchResultWrapper> searchResults = SOSLController.search(wrapper);
System.debug(LoggingLevel.INFO, '*** searchResults: \n' + JSON.serializePretty(searchResults));

结果:

总结:本篇只是描述一下SOSL的基本使用,还有很多细节使用以及限制没有涉及。本篇只起到抛砖引玉效果,如果项目中需要使用SOSL或者想要研究的,最好先自行查看文档。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏水击三千

Android Geocoder(位置解析)

Android中提供GPS定位服务,同时开发者可以对获得的位置信息进行解析,可以获得位置的详细信息。 1.gps定位 在Eclipse中建立android应用程...

307100
来自专栏一名合格java开发的自我修养

jdbc操作根据bean类自动组装sql,天啦,我感觉我实现了hibernate

场景:需要将从ODPS数仓中计算得到的大额可疑交易信息导入到业务系统的mysql中供业务系统审核。但是本系统是开放是为了产品化,要保证不同环境的可移植性,同时同...

21120
来自专栏zhisheng

渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(上)

上篇文章写了 ElasticSearch 源码解析 —— 环境搭建 ,其中里面说了启动 打开 server 模块下的 Elasticsearch 类:org.e...

14910
来自专栏爱撒谎的男孩

地址管理之省市区三级联动菜单

1.1K30
来自专栏Ryan Miao

使用dropwizard(6)-国际化-easy-i18n

前言 Dropwizard官方文档并没有提供国际化的模块,所以只能自己加。Spring的MessageResource用的很顺手,所以copy过来。 Easy...

400120
来自专栏Java成神之路

Java企业微信开发_05_消息推送之被动回复消息

微信加解密包 下载地址:http://qydev.weixin.qq.com/java.zip      ,此包中封装好了AES加解密方法,直接调用方法即可。

38520
来自专栏Phoenix的Android之旅

关于ThreadPoolExecutor要注意的问题

之前我们说过关于线程池的问题,我们可以用Executors的各种方法来获取不同的ThreadPoolExecutor来满足需求。但是当我们需要自定义线程池的时候...

8930
来自专栏跟着阿笨一起玩NET

以读取博客园随笔备份为例 将xml 序列化成json,再序列化成对象

资源下载:http://files.cnblogs.com/codealone/ConsoleApplication2.zip

11510
来自专栏岑玉海

Carbondata源码系列(一)文件生成过程

在滴滴的两年一直在加班,人也变懒了,就很少再写博客了,最近在进行Carbondata和hive集成方面的工作,于是乎需要对Carbondata进行深入的研究。 ...

66760
来自专栏java 成神之路

Semaphore 源码分析

29770

扫码关注云+社区

领取腾讯云代金券