Spec-Zone.ru › CouchDB 3.5

Руководство по разбиению на страницы

В этом руководстве объясняется, как разбить результаты представления на страницы. Разбиение на страницы — это шаблон пользовательского интерфейса (UI), позволяющий отображать большое количество строк (набор результатов), не загружая их все в интерфейс одновременно. Отображается подмножество фиксированного размера — страница, а рядом размещаются ссылки или кнопки «Следующая» и «Предыдущая», позволяющие перемещать область просмотра по набору результатов к соседней странице.

Предполагается, что вы знакомы с созданием и запросом документов и представлений, а также с различными параметрами запроса представления.

Пример данных

Чтобы получить данные для работы, создадим список групп, по одному документу на каждую группу:

{ "name":"Biffy Clyro" }

{ "name":"Foo Fighters" }

{ "name":"Tool" }

{ "name":"Nirvana" }

{ "name":"Helmet" }

{ "name":"Tenacious D" }

{ "name":"Future of the Left" }

{ "name":"A Perfect Circle" }

{ "name":"Silverchair" }

{ "name":"Queens of the Stone Age" }

{ "name":"Kerub" }

Представление

Нам нужна простая функция map, которая возвращает отсортированный по алфавиту список названий групп. Это должно быть несложно, но добавим дополнительную логику, чтобы исключить слова «The» и «A» в начале названий групп и правильно расположить их в списке:

function(doc) {
    if(doc.name) {
        var name = doc.name.replace(/^(A|The) /, "");
        emit(name, null);
    }
}

Результат представления — список названий групп, отсортированный по алфавиту. Предположим, что мы хотим отображать по пять названий групп и иметь ссылку на следующие пять названий, составляющих одну страницу, а также ссылку на предыдущие пять, если мы находимся не на первой странице.

В предыдущих документах мы узнали, как использовать параметры startkey, limit и skip. Здесь мы воспользуемся ими снова. Сначала посмотрим на полный набор результатов:

{"total_rows":11,"offset":0,"rows":[
    {"id":"a0746072bba60a62b01209f467ca4fe2","key":"Biffy Clyro","value":null},
    {"id":"b47d82284969f10cd1b6ea460ad62d00","key":"Foo Fighters","value":null},
    {"id":"45ccde324611f86ad4932555dea7fce0","key":"Tenacious D","value":null},
    {"id":"d7ab24bb3489a9010c7d1a2087a4a9e4","key":"Future of the Left","value":null},
    {"id":"ad2f85ef87f5a9a65db5b3a75a03cd82","key":"Helmet","value":null},
    {"id":"a2f31cfa68118a6ae9d35444fcb1a3cf","key":"Nirvana","value":null},
    {"id":"67373171d0f626b811bdc34e92e77901","key":"Kerub","value":null},
    {"id":"3e1b84630c384f6aef1a5c50a81e4a34","key":"Perfect Circle","value":null},
    {"id":"84a371a7b8414237fad1b6aaf68cd16a","key":"Queens of the Stone Age","value":null},
    {"id":"dcdaf08242a4be7da1a36e25f4f0b022","key":"Silverchair","value":null},
    {"id":"fd590d4ad53771db47b0406054f02243","key":"Tool","value":null}
]}

Настройка

Механизм разбиения на страницы очень прост:

  • Отобразить первую страницу

  • Если есть другие строки для отображения, показать ссылку «Следующая»

  • Отобразить следующую страницу

  • Если это не первая страница, показать ссылку «Предыдущая»

  • Если есть другие строки для отображения, показать ссылку «Следующая»

Или в виде фрагмента псевдокода на JavaScript:

var result = new Result();
var page = result.getPage();

page.display();

if(result.hasPrev()) {
    page.display_link('prev');
}

if(result.hasNext()) {
    page.display_link('next');
}

Разбиение на страницы

Чтобы получить первые пять строк из результата представления, используйте параметр запроса ?limit=5:

curl -X GET 'http://adm:pass@127.0.0.1:5984/artists/_design/artists/_view/by-name?limit=5'

Результат:

{"total_rows":11,"offset":0,"rows":[
    {"id":"a0746072bba60a62b01209f467ca4fe2","key":"Biffy Clyro","value":null},
    {"id":"b47d82284969f10cd1b6ea460ad62d00","key":"Foo Fighters","value":null},
    {"id":"45ccde324611f86ad4932555dea7fce0","key":"Tenacious D","value":null},
    {"id":"d7ab24bb3489a9010c7d1a2087a4a9e4","key":"Future of the Left","value":null},
    {"id":"ad2f85ef87f5a9a65db5b3a75a03cd82","key":"Helmet","value":null}
]}

Сравнив значение total_rows со значением limit, мы можем определить, есть ли ещё страницы для отображения. По элементу offset мы также знаем, что находимся на первой странице. Можно вычислить значение для skip=, чтобы получить результаты следующей страницы:

var rows_per_page = 5;
var page = (offset / rows_per_page) + 1; // == 1
var skip = page * rows_per_page; // == 5 for the first page, 10 for the second ...

Итак, отправим запрос CouchDB:

curl -X GET 'http://adm:pass@127.0.0.1:5984/artists/_design/artists/_view/by-name?limit=5&skip=5'

Обратите внимание: нужно использовать ' (одинарные кавычки), чтобы экранировать символ &, который является специальным для командной оболочки, в которой выполняется curl.

Результат:

{"total_rows":11,"offset":5,"rows":[
    {"id":"a2f31cfa68118a6ae9d35444fcb1a3cf","key":"Nirvana","value":null},
    {"id":"67373171d0f626b811bdc34e92e77901","key":"Kerub","value":null},
    {"id":"3e1b84630c384f6aef1a5c50a81e4a34","key":"Perfect Circle","value":null},
    {"id":"84a371a7b8414237fad1b6aaf68cd16a","key":"Queens of the Stone Age",
    "value":null},
    {"id":"dcdaf08242a4be7da1a36e25f4f0b022","key":"Silverchair","value":null}
]}

Реализовать методы hasPrev() и hasNext() довольно просто:

function hasPrev()
{
    return page > 1;
}

function hasNext()
{
    var last_page = Math.floor(total_rows / rows_per_page) +
        (total_rows % rows_per_page);
    return page != last_page;
}

Разбиение на страницы (альтернативный метод)

Описанный выше метод плохо работал с большими значениями skip до выхода CouchDB 1.2. Кроме того, в некоторых случаях может потребоваться следующий альтернативный метод даже в более новых версиях CouchDB. Например, если нужно исключить дублирование результатов. При использовании только skip во время разбиения на страницы могут добавляться новые документы, из-за чего сместится начальная позиция следующей страницы.

Правильное решение ненамного сложнее. Вместо того чтобы делить набор результатов на страницы одинакового размера, будем просматривать по 10 строк за раз и использовать startkey для перехода к следующим 10 строкам. Мы тоже используем skip, но только со значением 1.

Вот как это работает:

  • Запросить из представления rows_per_page + 1 строк

  • Отобразить rows_per_page строк, а ещё одну строку сохранить как next_startkey и next_startkey_docid

  • Сохранить startkey и next_startkey в качестве сведений о странице

  • Использовать значения next_* для создания ссылки на следующую страницу, а остальные — для создания ссылки на предыдущую

Перейти к следующей странице очень просто. Вместо того чтобы запрашивать для страницы 10 строк, запросите 11, отобразите только 10, а значения из 11-й строки используйте как startkey для следующей страницы. Чтобы создать ссылку на предыдущую страницу, достаточно передать текущее startkey следующей странице. Если предыдущего startkey нет, значит, мы на первой странице. Если в ответе получено rows_per_page строк или меньше, перестаём отображать ссылку на следующую страницу. Такой подход называется разбиением на страницы по связанному списку: мы переходим от страницы к странице, или от элемента списка к элементу, вместо перехода непосредственно к заранее вычисленной странице. Однако есть один нюанс. Сможете его заметить?

Ключи представления CouchDB не обязательно должны быть уникальными; для одного ключа может быть несколько записей индекса. Что, если записей индекса для ключа больше, чем строк должно быть на странице? startkey переходит к первой строке, и это стало бы проблемой, если бы CouchDB не предоставляла дополнительный параметр. Все ключи представления с одинаковым значением внутри сортируются по docid, то есть по идентификатору документа, создавшего эту строку представления. Для получения подмножеств этих строк можно использовать параметры startkey_docid и endkey_docid. Для разбиения на страницы нам по-прежнему не нужен endkey_docid, но startkey_docid очень полезен. Помимо startkey и limit, при разбиении на страницы также используется startkey_docid тогда и только тогда, когда дополнительная строка, полученная для поиска следующей страницы, имеет тот же ключ, что и текущий startkey.

Важно отметить, что параметры *_docid работают только вместе с параметрами *key и нужны лишь для дальнейшего сужения набора результатов представления с одним ключом. Сами по себе они не работают (исключение — встроенное представление _all_docs, которое уже отсортировано по идентификатору документа).

Преимущество этого подхода в том, что все операции с ключами выполняются в сверхбыстром B-дереве, лежащем в основе представления. Поиск страницы не требует без необходимости сканировать сотни и тысячи строк.

Переход к странице

Недостаток разбиения на страницы по связанному списку в том, что нельзя заранее вычислить строки для конкретной страницы по её номеру и количеству строк на странице. Перейти к определённой странице фактически невозможно. Если возникает такое опасение, наша первая реакция: «Даже Google так не делает!» — и обычно этого достаточно. Google всегда предлагает на первой странице ещё 10 страниц результатов. Только если нажать на вторую страницу (что на самом деле делают немногие), Google может отобразить меньше страниц. При просмотре результатов страницами появляются ссылки на 10 предыдущих и следующих страниц, но не более. Предварительное вычисление необходимых startkey и startkey_docid для 20 страниц — вполне выполнимая задача и практичная оптимизация, позволяющая узнать строки каждой страницы в наборе результатов, который может содержать десятки тысяч строк или больше.

Если действительно нужно обеспечить переход к любой странице во всём диапазоне документов (мы встречали приложения с таким требованием), можно по-прежнему поддерживать индекс целых чисел в качестве индекса представления и использовать гибридный подход к разбиению на страницы.

Copyright © 2025 The Apache Software Foundation — Licensed under the Apache License 2.0
https://docs.couchdb.org/en/3.5.1/ddocs/views/pagination.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API