function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Patrick DixonPatrick Dixon 

Help with SOSL

I'm building a site using force sites and I need a simple search facility to search my custom objects.  Unfortunately the field I need to search on is a text area and SOQL won't search text areas AFAIU.  So I figure I need to use SOSL, but the problem is that I can't figure out the syntax of what I need to do.

 

I understand the search bit:

 

String searchquery='FIND\'%'+searchText+'%\'IN ALL FIELDS RETURNING Content_Item__c (id,name), Content__c';
list<list<Content_Item__c>> searchList = search.query(searchquery);

 

but I don't understand what to do next or how to render the results in a VF page using a standard controller.

 

Can anybody point me at an example?  This must be a really common requirement for a web site, but I can seem to find any examples to get me started.

Patrick DixonPatrick Dixon

I have this custom controller:

 

public class CategorySearchController {
 
    // the results from the search
    public list<Content_Item__c> searchResults {
        get {return searchResults;}
        set ;
    }
 
    // the text in the search box
    public string searchText {
        get {
            if (searchText == null) searchText = 'Search';
            return searchText;
        }
        set;
    }
 
    // fired when the search button is clicked
    public PageReference search() {
 
        searchResults = new list<Content_Item__c>(); // init the list if it is null

        String wildcardSearchText='%'+ searchText + '%';  
        String searchquery='FIND \'' +wildcardsearchText+ '\' IN ALL FIELDS RETURNING Content_Item__c (id,name), Content__c';
        list<list<Content_Item__c>> searchList = search.query(searchquery);
        searchResults = ((List<Content_Item__c>)searchList[0]);

        searchquery = 'searchquery is '+searchquery ;

        ApexPages.Message testMsg = new ApexPages.Message(ApexPages.Severity.WARNING, searchquery);
        ApexPages.addMessage(testMsg);


        return null;
    }     
}

 

and this VF page

 

     <apex:form >
        <apex:pageBlock mode="edit" id="block">

            <apex:pageMessages />
 
            <apex:pageBlockSection >
                <apex:pageBlockSectionItem >
                    <apex:outputLabel for="searchText">Search for Categories</apex:outputLabel>
                    <apex:panelGroup >
                    <apex:inputText id="searchText" value="{!searchText}"/>
                    <!-- We could have rerendered just the resultsBlock below but we want the  -->
                    <!-- 'See Results' button to update also so that it is clickable. -->
                    <apex:commandButton value="Search" action="{!search}" rerender="block" status="status"/>
                    </apex:panelGroup>
                </apex:pageBlockSectionItem>
            </apex:pageBlockSection>
 
            <apex:actionStatus id="status" startText="Searching... please wait..."/>
            <apex:pageBlockSection title="Search Results" id="resultsBlock" columns="10">
                <apex:pageBlockTable value="{!searchResults}" var="c" rendered="{!NOT(ISNULL(searchResults))}">
                    <apex:column value="{!c.Name}" headerValue="Name"/>
                    <apex:column value="{!c.Id}" headerValue="Id"/>
                </apex:pageBlockTable>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>

 

But whatever search text I enter, I only ever get the column headings.

 

sfdcfoxsfdcfox

I'm not entirely certain what you're doing wrong, but take a look at my sample code and see if this helps you any:

 

 

<apex:page controller="multiSearch">
    <apex:form >
        <apex:pageBlock >
            <apex:pageBlockButtons >
                <apex:commandButton action="{!search}" value="Search"/>
            </apex:pageBlockButtons>
            <apex:inputText value="{!SearchTerm}"/>
        </apex:pageBlock>
        <apex:repeat value="{!searchResultLists}" var="sobjectType">
            <apex:pageBlock title="{!sobjectType}">
                <apex:dataTable value="{!searchResultLists[sobjectType]}" var="record">
                    <apex:column headerValue="Record Id" value="{!record.id}"/>
                    <apex:column headerValue="Record Name" value="{!record.name}"/>
                </apex:dataTable>
            </apex:pageBlock>
        </apex:repeat>
    </apex:form>
</apex:page>

 

public with sharing class multiSearch {

    public multiSearch() {
        searchResultLists = new map<string,List<Sobject>>();
    }

    public map<string,List<Sobject>> searchResultLists { get; set; }

    public String SearchTerm { get; set; }
    
    public void search() {
        for(list<sobject> results:search.query('find :SearchTerm in all fields returning account(id,name),contact(id,name)'))
            searchResultLists.put(results.getSobjectType().getDescribe().getLabel(),results);
    }
}

It's not as elaborate as yours, but I hope that this helps clarify things for you.

 

Patrick DixonPatrick Dixon

Thanks, that's definitely a help.

 

(Any elaboration on my part is almost certainly due to ignorance.)

 

Can anyone tell me how to deal with child/parent relationships using SOSL?  I want to return the found object, plus its child id fields and a parent field - so that I can create a clickable link to render the full record in its correct format.

 

If I use (Select field from child Limit 1) in the SOSL query, I just get all kinds of exceptions thrown, and I now seem to have generated an internal error for the salesforce staff to investigate.  Oh well, they should document this stuff better then!

Patrick DixonPatrick Dixon

Parent relationships are straightforward - parent__r.field__c in the SOSL query works fine.  So it's just child relationships I need to figure out.

sfdcfoxsfdcfox

The documentation does not provide an example for this type of query, so I presume that one does not exist (it is not supported). Also, you can't apparently use SOSL as a subquery to SOQL; I couldn't find an example for this syntax either. This leads me to believe that the correct method for doing something like this would be a two-parter that looks like this:

 

 

List<account> accountSearch = ((List<Account>)[find 'test' in all fields returning account(id)][0]);
list<account> accounts = [select id,name,(select id,firstname,lastname from contacts) from account where id in :accountSearch];

 

 

Patrick DixonPatrick Dixon

I'm afraid I'm out of my depth with sObjects and

 

public with sharing class multiSearch {

    public multiSearch() {
        searchResultLists = new map<string,List<Sobject>>();
    }

    public map<string,List<Sobject>> searchResultLists { get; set; }

    public String SearchTerm { get; set; }
    
    public void search() {
        for(list<sobject> resultsSearch:search.query('find :SearchTerm in all fields returning Content_Item__c(id,name,synopsis__c,content_type__r.Read_More_Dest__c,content_type__r.name)'))
            list<sobject> results = [select id,name,synopsis__c,content_type__r.Read_More_Dest__c,content_type__r.name,
                (select id from Contents__r limit 1) from Contact_Item__c where id in :resultsSearch];
            searchResultLists.put(results.getSobjectType().getDescribe().getLabel(),results);
    }
}


Gives me:

 

Save error: sObject type 'Contact_Item__c' is not supported. If you are attempting to use a custom object, be sure to append the '__c' after the entity name

sfdcfoxsfdcfox

That should be Content_Item__c, not Contact_Item__c. A simple typo.

Patrick DixonPatrick Dixon

Ahh - thanks!  I'm going blind ...

 

Unfortunately that just leads to:

 

Save error: Variable does not exist: results

 

And if I change it to:

 

public with sharing class multiSearch {

    public multiSearch() {
        searchResultLists = new map<string,List<Sobject>>();
    }

    public map<string,List<Sobject>> searchResultLists { get; set; }

    public String SearchTerm { get; set; }
    
    public void search() {
        list<sobject> results;
        for(list<sobject> resultsSearch:search.query('find :SearchTerm in all fields returning Content_Item__c(id,name,synopsis__c,content_type__r.Read_More_Dest__c,content_type__r.name)'))
            results = [select id,name,synopsis__c,content_type__r.Read_More_Dest__c,content_type__r.name,
                (select id from Contents__r limit 1) from Content_Item__c where id in :resultsSearch];
            searchResultLists.put(results.getSobjectType().getDescribe().getLabel(),results);
    }
}

 

I get:

 

Save error: Only concrete SObject types can be used as Id binds

 

Patrick DixonPatrick Dixon

So, dunno how the sObject stuff should/would/could work, but

 

public with sharing class multiSearch {

    public multiSearch() {
        searchResultLists = new List<Content_Item__c>();
    }

    public List<Content_Item__c> searchResultLists { get; set; }

    public String SearchTerm { get; set; }
    
    public void search() {
        for(List<Content_Item__c> results:search.query('find :SearchTerm in all fields returning Content_Item__c(id,name,synopsis__c,content_type__r.Read_More_Dest__c,content_type__r.name)'))
            searchResultLists = [select id,name,synopsis__c,content_type__r.Read_More_Dest__c,content_type__r.name,
                (select id from Contents__r limit 1) from Content_Item__c where id in :results];
    }
}

 

does for me.

 

Thanks for the help - much appreciated.

sfdcfoxsfdcfox

SObject is an abstract interface to an unknown type of record. Each of your objects are "concrete" types that extend the abstract SObject type. Since your search returns only one type of record, you can certainly use the for loop as you've written it (not strictly necessary, but it doesn't hurt either). My example showed how you might return multiple objects and display all the results at once, similiar in design to how the Search bar displays its results. As long as your code is getting you where you need to go, that's the most important thing.

Patrick DixonPatrick Dixon

The usefulness of sObjects seems somewhat limited though - because as soon as you add a field that is not universal on all of the objects, you run into problems.  I certainly couldn't get my code to work using them - because of that  Only concrete SObject types can be used as Id binds error.


Still it's early days - I'm just blundering around trying to get stuff to work - so I expect I'll figure out their use sooner or later ...


Thanks again.

 

PS. I don't understand what the syntax would be without the for loop - I just end up with loads of problems over list<list< and list< assignments.

sfdcfoxsfdcfox

It's true that SObject has limited utility compared to a "concrete" sobject. However, it allows you to have a polymorphic interface for an unknown type of record. For example, you can create a Batchable interface to iterate over any type of record and perform processing, without having to hard-code a particular type of SObject in place. It allows you to also create such interfaces as a report-generating Apex Class that can take some arguments as parameters, including the type of an SObject, and perform processing without regards to the specific type of record you're working with. They are not always as useful as concrete objects, but they definitely have benefits over using specialized objects.