在工作中,我們更多操作的是一個表的對象,所以我們對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或者想要研究的,最好先自行查看文檔。