Handle Salesforce SOQL Offset Limitation

Handle Salesforce SOQL Offset Limitation

In Salesforce SOQL, we cannot set the Offset value more than 2000.

In order to overcome the Offset limitation, we can avoid using it and order the records based on the Id using ORDER BY Clause in the SOQL and retrieve them by filtering in subsequent requests.

Example:

Request 1: Fetch 50 records. Make a note of the last record Id.

Request 2: Fetch next 50 records where Id greater than the last record Id.

Request n: Request for the last batch of records where Id greater than the last record Id.

Sample Apex Code:

@RestResource( urlMapping='/OpportunitiesExport/' )
global with sharing class RESTAPIController {
    
    @HttpGet
    global static void fetchOpportunities() {
        
        Integer intLimit = 50;
        String strResponse;
        Map < String, String > mapResponse = new Map < String, String >();
        RestRequest req = RestContext.request;
        String strStageName = req.params.containsKey( 'StageName' ) ?
            req.params.get( 'StageName' ).toString() :
            null;
        String strLastOpptyId = req.params.containsKey( 'LastOpptyId' ) ?
            req.params.get( 'LastOpptyId' ).toString() :
            null;
        Integer intOffset = req.params.containsKey( 'Offset' ) ?
            Integer.valueOf( req.params.get( 'Offset' ) ) :
            0;
        
        try {
            
            List < Opportunity > listOpptys = new List < Opportunity >();
            JSONGenerator gen = JSON.createGenerator( true );
            gen.writeStartObject();
            Integer opptyCount = [
                SELECT COUNT()
                FROM Opportunity
                WHERE StageName =: strStageName
            ];
            gen.writeNumberField(
                'totalCount', 
                opptyCount
            );
            
            if ( String.isBlank( strLastOpptyId ) ) {
                
                listOpptys = [
                    SELECT Id, Name
                    FROM Opportunity
                    WHERE StageName =: strStageName
                    ORDER BY Id 
                    LIMIT :intLimit
                ];
                
            } else {
                
                listOpptys = [
                    SELECT Id, Name
                    FROM Opportunity
                    WHERE StageName =: strStageName
                    AND Id > :strLastOpptyId
                    ORDER BY Id 
                    LIMIT :intLimit
                ];
                
            }
            
            gen.writeNumberField(
                'retrievedCount', 
                listOpptys.size()
            );
            
            if ( opptyCount - intOffset > intLimit ) {
                
                gen.writeBooleanField(
                    'queryCompleted', 
                    false
                );            
                gen.writeStringField(
                    'LastOpptyId', 
                    listOpptys.get(
                        listOpptys.size() - 1
                    ).Id
                );
                intOffset = intOffset + intLimit;                
                gen.writeStringField(
                    'nextRequestURL', 
                    '/services/apexrest/OpportunitiesExport?Offset='
                    + intOffset + '&StageName=Prospecting&LastOpptyId=' +
                    listOpptys.get(
                        listOpptys.size() - 1
                    ).Id
                );
                
            } else {
                
                gen.writeBooleanField(
                    'queryCompleted', 
                    true
                );               
                gen.writeNullField(
                    'nextRequestURL'
                );
                
            }
            
            Map < String, List < Opportunity > > mapRecords = new Map < String, List < Opportunity > > {
                'records' => listOpptys
            };        
            gen.writeFieldName( 'records' );
            gen.writeObject(
                listOpptys
            );                       
            gen.writeEndObject();
            strResponse = gen.getAsString();
            
        } catch( Exception e ) {
            
            mapResponse.put(
                'errorMessage', 
                e.getMessage() 
            );
            strResponse = JSON.serialize(
                mapResponse
            );     
            
        }
           
        RestContext.response.addHeader(
            'Content-Type', 
            'application/json'
        );
        RestContext.response.responseBody = Blob.valueOf(
            strResponse
        ); 
        
    }
    
}

Leave a Reply