Реализация пагинации (последовательная обработка данных)

Итеративная обработка данных

В Серверном API нет методов, реализующих пагинацию, как, например, в операции GET Table API. Однако без пагинации, при выборке большого количества записей из таблицы, мы можем столкнуться с проблемой снижения производительности системы и ограничением по памяти.
Для решения данной проблемы можно разделить обработку данных на “порции”.
Код ниже итеративно извлекает и обрабатывает все записи из таблицы itsm_task блоками по 1000 записей:

const LIMIT = 1000;
let recordCount = LIMIT;
let lastRecordId = '0';
while (recordCount === LIMIT) {
    const record = new SimpleRecord('itsm_task');
    record.addQuery('sys_id', '>', lastRecordId);
    record.orderBy('sys_id');
    record.setLimit(LIMIT);
    record.query();
    recordCount = record.getRowCount();
    while (record.next()) {
        lastRecordId = record.sys_id;
    }
}

Постраничный доступ к данным

На основе представленной логики, можно организовать постраничную выборку данных, например, через функцию getNextPage, которая реализует одну итерацию пагинации (одну “страницу” данных) из таблицы, задаваемой через параметр tableName, начиная с записи после startRecordId. Начальное значение startRecordId должно быть равно максимально возможному sys_id, чтобы начать с новых записей, в противном случае на первой “странице” будут доступны наиболее старые записи.

function getNextPage(tableName, limit = 10, startRecordId = 'ffffffffffffffffffffffffffffffff') {
    const record = new SimpleRecord(tableName);
    record.addQuery('sys_id', '<', startRecordId);
    record.orderByDesc('sys_id');
    record.setLimit(limit);
    record.selectAttributes(['sys_id', 'number']);
    record.query();
    
    const data = [];
    let lastRecordId = startRecordId;
    while (record.next()) {
        lastRecordId = record.getValue('sys_id');
        data.push(record.getAttributes());
    }

    return { data, lastRecordId }; // {"data":[],"lastRecordId":""}
}

Если нужно получить n-ую страницу, можно сделать обертку в цикл:

function getPage(tableName, pageNumber, pageSize = 10) {
    let lastRecordId = 'ffffffffffffffffffffffffffffffff';
    let currentPage = 1;
    let page;

    while (currentPage <= pageNumber) {
        page = getNextPage(tableName, pageSize, lastRecordId);
        lastRecordId = page.lastRecordId;
        currentPage++;
    }

    return page; // {"data":[],"lastRecordId":""}
}

Важно заметить, что функция getPage неэффективна при больших значениях pageNumber, т.к. при попытке получить, например, 100-ю страницу, функция вызывает getNextPage 100 раз. Это не критично при малых объёмах, но сильно влияет на производительность при больших значениях pageNumber.

Table API

Проблему производительности можно решить воспользовавшись Table API и SimpleRestRequest. С помощью SimpleWebService sws.restRequestV1() создается объект SimpleRestRequest и далее с помощью его методов setRequestMethod, setRequestUrl и прочих, формируется запрос к Table API:

function getPage(tableName, pageNumber = 1, pageSize = 10) {
    const request = sws.restRequestV1();
    request.setRequestMethod('GET');
    const instanceUri = ss.getProperty('simple.instance.uri');
    request.setRequestUrl(`https://${instanceUri}/rest/v1/table/${tableName}`);
    request.setQueryParameter('sysparm_fields', 'number,sys_id');
    request.setRequestHeader('Authorization', `Bearer XXXXXXXX`);
    request.setQueryParameter('sysparm_page', pageNumber.toString());
    request.setQueryParameter('sysparm_limit', pageSize.toString());
    const response = JSON.parse(request.execute().getBody());
    return response; // {"status":"OK","data":[]}
}

Важно: Table API требует авторизации, поддерживаются Basic Auth и Bearer Token.

4 лайка