Saltar a contenido

Paginado

Documentación sobre Paginación usando Offset-based y Marker-based

La paginación es una técnica común utilizada en APIs para dividir grandes conjuntos de datos en fragmentos más pequeños, lo que permite una mejor administración y recuperación de la información. En este documento, exploraremos dos enfoques populares para la paginación: Offset-based y Marker-based.

Offset-based Pagination

La paginación basada en offset implica especificar cuántos elementos se deben omitir desde el principio antes de recuperar un número determinado de elementos. Esto se logra utilizando los parámetros "offset" y "limit" en la llamada a la API.

Request

La petición al endpoint debe contener los siguientes QueryParam

Query Param Type Default
offset Integer 0 El cero indica que arranca a recorrer desde el inicio
limit Integer Depende de la API El número máximo de entradas a devolver. Si el valor excede el máximo, se utilizará el valor máximo.

Response

La respuesta de la API debe contener la siguiente información

Field Type
entries Array Lista de elementos con información pertinente a la busqueda.
offset Integer El offset usado en la petición
limit Integer El limit utilizado en la petición
totalCount Integer Total de elementos encontrados para la petición

Pasos para implementar Offset-based Pagination:

  1. Definición de Parámetros:

  2. limit: El número máximo de elementos que se recuperarán en una sola solicitud.

  3. offset: La cantidad de elementos que se deben omitir desde el principio.

  4. Solicitud Inicial: Realiza una solicitud al servicio web con los parámetros limit y offset adecuados para obtener la primera página de resultados.

  5. Siguiente Página: Para obtener la siguiente página de resultados, realiza otra solicitud con el mismo valor de limit y un valor de offset ajustado. El nuevo valor de offset sería el valor del offset de la solicitud anterior más el limit.


Ejemplos programaticos básicos

using System;
using System.Collections.Generic;

namespace OffsetPaginationExample
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> data = GenerateSampleData();

            int pageSize = 5; // Tamaño de página
            int offset = 0;   // Valor de offset inicial

            while (offset < data.Count)
            {
                List<string> currentPage = GetPage(data, offset, pageSize);
                Console.WriteLine("Página actual:");
                foreach (string item in currentPage)
                {
                    Console.WriteLine(item);
                }

                Console.WriteLine("Presiona Enter para obtener la siguiente página...");
                Console.ReadLine();

                offset += pageSize; // Incrementar el offset para obtener la siguiente página
            }

            Console.WriteLine("Fin de la paginación.");
        }

        static List<string> GetPage(List<string> data, int offset, int pageSize)
        {
            return data.Skip(offset).Take(pageSize).ToList();
        }

        static List<string> GenerateSampleData()
        {
            List<string> data = new List<string>();
            for (int i = 1; i <= 20; i++)
            {
                data.Add($"Elemento {i}");
            }
            return data;
        }
    }
}
package main

import (
    "fmt"
)

func getPage(data []string, offset int, pageSize int) []string {
    if offset >= len(data) {
        return nil
    }
    end := offset + pageSize
    if end > len(data) {
        end = len(data)
    }
    return data[offset:end]
}

func main() {
    data := make([]string, 20)
    for i := 0; i < 20; i++ {
        data[i] = fmt.Sprintf("Element %d", i+1)
    }

    pageSize := 5
    offset := 0

    for offset < len(data) {
        currentPage := getPage(data, offset, pageSize)
        fmt.Println("Página actual:")
        for _, item := range currentPage {
            fmt.Println(item)
        }

        var input string
        fmt.Println("Presiona Enter para obtener la siguiente página...")
        fmt.Scanln(&input)
        offset += pageSize
    }

    fmt.Println("Fin de la paginación.")
}
def get_page(data, offset, page_size):
    return data[offset : offset + page_size]

def main():
    data = [f"Element {i}" for i in range(1, 21)]
    page_size = 5
    offset = 0

    while offset < len(data):
        current_page = get_page(data, offset, page_size)
        print("Página actual:")
        for item in current_page:
            print(item)

        input("Presiona Enter para obtener la siguiente página...")
        offset += page_size

    print("Fin de la paginación.")

if __name__ == "__main__":
    main()
function get_page(data, offset, page_size) {
    return data.slice(offset, offset + page_size);
}

function main() {
    let data = [];
    for (let i = 1; i <= 20; i++) {
        data.push(`Elemento ${i}`);
    }

    let page_size = 5;
    let offset = 0;

    while (offset < data.length) {
        let current_page = get_page(data, offset, page_size);
        console.log("Página actual:");
        for (let item of current_page) {
            console.log(item);
        }

        prompt("Presiona Enter para obtener la siguiente página...");
        offset += page_size;
    }

    console.log("Fin de la paginación.");
}

main();

Ejemplos APIs

Request
curl -i -X GET "https://api.box.com/2.0/folders/0/items?offset=2000&limit=1000" \
     -H "Authorization: Bearer <ACCESS_TOKEN>"
Response
{
  "entries": [
    {
      "id": "12345",
      "etag": "1",
      "type": "file",
      "sequence_id": "3",
      "name": "Contract.pdf",
      "sha1": "85136C79CBF9FE36BB9D05D0639C70C265C18D37",
      "file_version": {
        "id": "12345",
        "type": "file_version",
        "sha1": "134b65991ed521fcfe4724b7d814ab8ded5185dc"
      }
    }
  ],
  "limit": 1000,
  "offset": 2000,
  "order": [
    {
      "by": "type",
      "direction": "ASC"
    }
  ],
  "totalCount": 5000
}

Marker-based Pagination

La paginación basada en marcadores implica el uso de un marcador o un identificador único para obtener la siguiente página de resultados. Aquí tienes los pasos para implementar la paginación basada en marcadores:

Request

La petición al endpoint debe contener los siguientes QueryParam

Query Param Type Default
marker string vacio o null indican que se debe arrancar a recorrer por el inicio
limit Integer Depende de la API El número máximo de entradas a devolver. Si el valor excede el máximo, se utilizará el valor máximo.

Response

La respuesta de la API debe contener la siguiente información

Field Type
entries Array Lista de elementos con información pertinente a la busqueda.
next String El valor que se puede utilizar como valor de marcador para obtener la siguiente página de resultados. Si este valor es null o una cadena vacía, no hay más resultados que obtener.
limit Integer El limit utilizado en la petición

Pasos para implementar Marker-based Pagination:

  1. Definición de Parámetros:

    • limit: El número máximo de elementos que se recuperarán en una sola solicitud.
    • marker: El marcador o identificador del último elemento de la página anterior.
  2. Solicitud Inicial: Realiza una solicitud al servicio web con el parámetro limit para obtener la primera página de resultados.

  3. Siguiente Página: Para obtener la siguiente página de resultados, toma el marcador o identificador del último elemento de la página actual y úsalo como valor para el parámetro marker en la siguiente solicitud.

Ejemplos programaticos básicos

using System;
using System.Collections.Generic;
using System.Linq;

namespace MarkerPaginationExample
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> data = GenerateData(50); // Genera una lista de datos ficticios

            int pageSize = 10; // Tamaño de cada página
            string startMarker = null; // Marcador de inicio de la página

            do
            {
                List<string> currentPage = GetPage(data, startMarker, pageSize, out startMarker);

                Console.WriteLine("Página:");
                foreach (string item in currentPage)
                {
                    Console.WriteLine(item);
                }

                Console.WriteLine("Presiona Enter para cargar la siguiente página...");
                Console.ReadLine();

            } while (startMarker != null);

            Console.WriteLine("Fin de la paginación.");
        }

        static List<string> GenerateData(int count)
        {
            List<string> data = new List<string>();
            for (int i = 1; i <= count; i++)
            {
                data.Add($"Elemento {i}");
            }
            return data;
        }

        static List<string> GetPage(List<string> data, string startMarker, int pageSize, out string nextStartMarker)
        {
            List<string> page = new List<string>();
            int startIndex = string.IsNullOrEmpty(startMarker) ? 0 : data.IndexOf(startMarker)-1 ;

            page = data.Skip(startIndex).Take(pageSize).ToList();

            nextStartMarker = (startIndex + page.Count < data.Count) ? data[startIndex + page.Count] : null;
            return page;
        }
    }
}
package main

import (
    "fmt"
)

func main() {
    data := generateData(50) // Genera una lista de datos ficticios
    pageSize := 10           // Tamaño de cada página
    var startMarker string   // Marcador de inicio de la página

    for {
        currentPage, newStartMarker := getPage(data, startMarker, pageSize)
        startMarker = newStartMarker

        fmt.Println("Página:")
        for _, item := range currentPage {
            fmt.Println(item)
        }

        fmt.Println("Presiona Enter para cargar la siguiente página...")
        fmt.Scanln()

        if newStartMarker == "" {
            break
        }
    }

    fmt.Println("Fin de la paginación.")
}

func generateData(count int) []string {
    data := make([]string, 0)
    for i := 1; i <= count; i++ {
        data = append(data, fmt.Sprintf("Elemento %d", i))
    }
    return data
}

func getPage(data []string, startMarker string, pageSize int) ([]string, string) {
    var page []string
    startIndex := 0

    for i, item := range data {
        if item == startMarker {
            startIndex = i -1
            break
        }
    }

    for i := startIndex; i < len(data) && len(page) < pageSize; i++ {
        page = append(page, data[i])
    }

    nextStartMarker := ""
    if startIndex+len(page) < len(data) {
        nextStartMarker = data[startIndex+len(page)]
    }

    return page, nextStartMarker
}
def main():
    data = generate_data(50)  # Genera una lista de datos ficticios
    page_size = 10  # Tamaño de cada página
    start_marker = None  # Marcador de inicio de la página

    while True:
        current_page, new_start_marker = get_page(data, start_marker, page_size)
        start_marker = new_start_marker

        print("Página:")
        for item in current_page:
            print(item)

        input("Presiona Enter para cargar la siguiente página...")

        if not new_start_marker:
            break

    print("Fin de la paginación.")

def generate_data(count):
    data = []
    for i in range(1, count + 1):
        data.append(f"Elemento {i}")
    return data

def get_page(data, start_marker, page_size):
    page = []
    start_index = 0

    if start_marker:
        start_index = data.index(start_marker) -1

    for i in range(start_index, len(data)):
        page.append(data[i])
        if len(page) == page_size:
            break

    next_start_marker = None
    if start_index + len(page) < len(data):
        next_start_marker = data[start_index + len(page)]

    return page, next_start_marker

if __name__ == "__main__":
    main()
function main() {
    const data = generateData(50); // Genera un array de datos ficticios
    const pageSize = 10; // Tamaño de cada página
    let startMarker = null; // Marcador de inicio de la página

    while (true) {
        const { currentPage, newStartMarker } = getPage(data, startMarker, pageSize);
        startMarker = newStartMarker;

        console.log("Página:");
        currentPage.forEach(item => {
            console.log(item);
        });

        prompt("Presiona Enter para cargar la siguiente página...");

        if (!newStartMarker) {
            break;
        }
    }

   console.log("Fin de la paginación.");
}

function generateData(count) {
    const data = [];
    for (let i = 1; i <= count; i++) {
        data.push(`Elemento ${i}`);
    }
    return data;
}

function getPage(data, startMarker, pageSize) {
    const page = [];
    let startIndex = 0;

    if (startMarker) {
        startIndex = data.indexOf(startMarker) -1;
    }

    for (let i = startIndex; i < data.length && page.length < pageSize; i++) {
        page.push(data[i]);
    }

    const nextStartMarker = startIndex + page.length < data.length ? data[startIndex + page.length] : null;

    return { currentPage: page, newStartMarker: nextStartMarker };
}

main();

Ejemplos APIs

Request
curl -i -X GET "https://api.box.com/2.0/folders/0/items?marker=897f33c7-5c4d-4cb4-8326-75b694751387&limit=1" \
     -H "Authorization: Bearer <ACCESS_TOKEN>"
Response
{
  "entries": [
    {
      "id": "12345",
      "etag": "1",
      "type": "file",
      "sequence_id": "3",
      "name": "Contract.pdf",
      "sha1": "85136C79CBF9FE36BB9D05D0639C70C265C18D37",
      "file_version": {
        "id": "12345",
        "type": "file_version",
        "sha1": "134b65991ed521fcfe4724b7d814ab8ded5185dc"
      }
    }
  ],
  "limit": 1,
  "next": "ba8d7715-8db0-48e0-a7e3-fce81a3997fb",
}

¿Cuál Método de Paginación Elegir?

Cada método de paginación tiene un ámbito de aplicación específico. El paginado basado en "offset" es adecuado cuando calcular el número total de elementos almacenados no implica un alto costo computacional. Sin embargo, a medida que la base de datos crece, este cálculo puede volverse más lento y afectar el rendimiento.

En situaciones donde el rendimiento es una preocupación, especialmente cuando la base de datos es grande, se recomienda utilizar el paginado basado en "marker" (marcador). Este método tiene la ventaja de que no requiere conocer el número total de registros. Simplemente nos posicionamos en el siguiente registro de interés, lo que lo hace más eficiente para bases de datos en crecimiento.