[SalesForce] Workaround for Offset 2000 limit on SOQL Query

Problem

I'm trying to make a standard query pagination. I'm using SOAP API.

The logic is classic: LIMIT recordsByPage OFFSET (recordsByPage*pageNumber)+1,

It works fine, however, I'm facing the 2000 limit of OFFSET:

The maximum offset is 2,000 rows. Requesting an offset greater than
2,000 will result in a NUMBER_OUTSIDE_VALID_RANGE error.

I advice of SF to solve this limit is to create a QueryLocator and use the queryMore.

We have more than 100k records, and the recordsByPage ~50. If I need to go record #50000, It will take 1000 queryMore.
Instead of that, if I use the max (2000) batch size in the queryLocator then I will require 25 queryMore.

From my view this approach is inefficient.

Question

What is the most efficient (performance + less api calls) way to make a query pagination ?

Best Answer

QueryMore is the appropriate means of accomplishing this if you're getting data all at once. For pagination, such as in a website or portal, sort by some value then use filters. I would recommend a field with a high cardinality (e.g. many unique values) because it makes pagination that much easier.

I've posted this answer elsewhere, but I'll post it here, too. Let's say you're sorting by CreateDate or ID. Your first query would look like this:

SELECT Id, Name, CreatedDate FROM Account ORDER BY CreatedDate LIMIT 2000

At this point, you've got 2000 records to work with. Take the 2000th ID, and add it to your next query:

SELECT Id, Name, CreatedDate FROM Account WHERE CreatedDate > "Last-Returned-Created-Date" ORDER BY CreatedDate LIMIT 2000

At this point, you'll get your next 2000 records. You can rinse and repeat as necessary. If you want to go back a page, just reverse the order (but you'll also have to reverse the results):

SELECT Id, Name, CreatedDate FROM Account WHERE CreatedDate < "First-Returned-Created-Date" ORDER BY CreatedDate DESC LIMIT 2000

For better performance, you may just as well consider client-side caching with a periodic refresh of the data using "getUpdated()" and "getDeleted()" (SOAP API replication).

In any case, you can't just "go" to the 50,000th record initially; you have to step your way there. Caching would provide the biggest benefit in this regards and should be considered if you have large data sets.

Alternatively, you could also write a custom web service to quickly step through records using the method described above:

global webservice Id findOffset(String query) {
    SObject[] results = Database.query(query);
    return results[results.size()-1].Id;
}

Since Apex Code has a limit of 50,000 records, you can skip 50,000 rows at a time using this technique, bringing your total efficiency up to 1 API call per 50,000 rows, plus one for the final query that gives you the results for the page.

Related Topic