Перейти к содержимому

Фильтрация и сортировка

Фильтрация — ограничение набора данных по условиям (равенство, сравнение, диапазон, список, частичное совпадение, NULL). Сортировка — упорядочивание по одному или нескольким полям (ASC/DESC). Способы передачи: query параметры (GET /users?status=active&age_gte=18), тело POST (сложные фильтры), путь URL (предопределённые фильтры). Для стабильной пагинации добавляйте уникальное поле (id) в конец сортировки. Комбинация: фильтрация → сортировка → пагинация. Типичные ошибки: отсутствие фильтрации по умолчанию (возврат всех записей), сортировка по неиндексированным полям, нестабильная сортировка (дублирование/пропуск записей), ведущий wildcard в поиске (медленно).

Введение: Тысячи ответов на один запрос

Представьте, что вы зашли в интернет-магазин электроники. Вы видите кнопки: “Показать только смартфоны”, “Цена по возрастанию”, “Бренд: Apple, Samsung”, “Цена: от 10000 до 50000”. Вы нажимаете на них, и список товаров мгновенно меняется. Магазин не показывает вам все 10 000 товаров — он показывает только те, которые соответствуют вашим условиям, и в том порядке, который вы выбрали.

В API то же самое. Клиент часто не хочет получать все записи. Ему нужны только те, которые удовлетворяют определённым условиям (фильтрация), и в определённом порядке (сортировка).

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

Сортировка — это упорядочивание результата по одному или нескольким полям (по возрастанию или убыванию).

Без фильтрации и сортировки API был бы бесполезен для пользователей. Никто не хочет просматривать миллион записей в поисках нужной. Клиент должен иметь возможность задать вопрос: “Дай мне только активных пользователей из Москвы, отсортированных по дате регистрации”.

Фильтрация

Простая фильтрация (равенство)

Самый простой способ — передать значения полей как параметры запроса.

GET /users?status=active
GET /users?city=Москва
GET /users?status=active&city=Москва

Реализация на сервере (SQL):

-- WHERE status = 'active'
-- WHERE city = 'Москва'
-- WHERE status = 'active' AND city = 'Москва'

Фильтрация с операторами сравнения

Не всегда нужно точное равенство. Часто нужны “больше”, “меньше”, “между”, “содержит”.

GET /users?age_gte=18          # возраст >= 18
GET /users?age_lte=65          # возраст <= 65
GET /users?price_gt=1000       # цена > 1000
GET /products?price_between=1000,5000  # цена от 1000 до 5000
GET /users?created_at_after=2024-01-01
GET /products?name_contains=iphone  # содержит подстроку

Варианты синтаксиса:

СтильПримерПримечание
Суффиксыage_gte=18Просто, понятно
Префиксыgte_age=18Реже
Синтаксис JSONfilter={"age":{"gte":18}}POST запрос
Синтаксис RSQLfilter=age>=18Сложный, мощный
Синтаксис параметровage=gte:18Альтернатива

Фильтрация по диапазону

# Цена от 100 до 500
GET /products?price_min=100&price_max=500

# Дата от и до
GET /orders?from=2024-01-01&to=2024-12-31

# Или с операторами
GET /products?price_gt=100&price_lt=500

Фильтрация по списку значений (IN)

GET /users?status_in=active,pending
GET /products?category_in=electronics,computers
GET /orders?status_in=completed,shipped

Фильтрация по частичному совпадению (поиск)

# Содержит подстроку (без учёта регистра)
GET /users?name_like=иван
GET /products?description_contains=iphone

# Начинается с
GET /users?name_starts_with=a

# Заканчивается на
GET /users?name_ends_with=ov

Важно: Поиск с ведущим wildcard (%phone) не использует индексы и медленный.

# Быстро (использует индекс)
GET /products?name_starts_with=iphone

# Медленно (не использует индекс)
GET /products?name_contains=phone

Фильтрация по NULL

GET /users?phone_is_null=true
GET /users?email_is_null=false

Фильтрация по булевым полям

GET /users?is_active=true
GET /users?is_deleted=false
GET /users?is_verified=1

Сложная фильтрация (логические операторы)

Для сложных условий нужны операторы AND, OR, NOT.

# (status=active AND age>=18) OR (status=vip)
GET /users?filter=(status=active,age>=18);(status=vip)

RSQL (FIQL) формат:

AND = ;
OR = ,
NOT = !
GET /users?filter=status==active;age>=18,status==vip
GET /products?filter=price=gt=1000;category==electronics
GET /users?filter=!status==deleted

JSON формат (через POST):

POST /users/search
Content-Type: application/json

{
    "filter": {
        "and": [
            {"field": "status", "op": "eq", "value": "active"},
            {"field": "age", "op": "gte", "value": 18}
        ],
        "or": [
            {"field": "status", "op": "eq", "value": "vip"}
        ]
    }
}

Где передавать фильтры

СпособПримерКогда использовать
Query параметрыGET /users?status=activeПростые фильтры, GET запросы
Путь (URL)GET /users/activeПредопределённые фильтры
Тело запросаPOST /users/searchСложные фильтры, JSON
ЗаголовкиX-Filter: status=activeРедко

Сортировка

Сортировка по одному полю

# По возрастанию (ASC)
GET /users?sort=name

# По убыванию (DESC)
GET /users?sort=-name
GET /users?sort=name.desc
GET /users?order_by=name&order_dir=desc

Сортировка по нескольким полям

# Сначала по статусу (по возрастанию), потом по дате регистрации (по убыванию)
GET /users?sort=status,-created_at

# Альтернативный синтаксис
GET /users?sort=status,created_at&order=asc,desc

Сортировка и пагинация

Сортировка должна быть стабильной, особенно при пагинации. Добавляйте уникальное поле (обычно id) в конец сортировки.

# Плохо (нестабильно, если есть одинаковые created_at)
GET /users?sort=-created_at

# Хорошо (стабильно)
GET /users?sort=-created_at,id

Комбинация фильтрации, сортировки и пагинации

GET /products?category=electronics&price_lte=50000&sort=-price,id&limit=20&cursor=123

Что делает этот запрос:

  • Фильтрует товары: категория “electronics”, цена ≤ 50000
  • Сортирует по убыванию цены, затем по возрастанию ID
  • Возвращает 20 товаров после курсора 123

Продвинутые техники

Фильтрация по связанным ресурсам

# Заказы пользователей из Москвы
GET /orders?customer_city=Москва

# Товары, у которых есть отзывы с оценкой 5
GET /products?reviews_rating=5

Фильтрация по наличию связи

# Пользователи, у которых есть заказы
GET /users?has_orders=true

# Пользователи без заказов
GET /users?has_orders=false

Фильтрация по вычисляемым полям

# Полная цена = цена * количество + доставка
GET /orders?total_gt=1000

На сервере:

SELECT * FROM orders 
WHERE (price * quantity + delivery_cost) > 1000

Фильтрация по дате и времени

# Конкретный день
GET /events?date=2024-01-15

# Диапазон
GET /events?from=2024-01-01&to=2024-12-31

# Последние 7 дней
GET /events?since=7d

# Временные метки (Unix timestamp)
GET /events?after=1704067200

Пользовательские заголовки для пагинации/фильтрации

GET /users
X-Page: 2
X-Per-Page: 20
X-Sort: name,id
X-Filter: status=active

Нестандартно, лучше использовать query параметры.

Примеры полного API

Пример 1: Пользователи

GET /users?status=active&city=Москва&age_gte=18&sort=-created_at,id&limit=10&cursor=123

Пример 2: Товары

GET /products?category=electronics&price_between=1000,50000&in_stock=true&sort=price,id&limit=20&page=3

Пример 3: Заказы

GET /orders?customer_id=123&from=2024-01-01&to=2024-12-31&status_in=completed,shipped&sort=-created_at,id

Пример 4: Поиск со сложными фильтрами

POST /products/search
Content-Type: application/json

{
    "filter": {
        "and": [
            {"field": "category", "op": "in", "value": ["electronics", "computers"]},
            {"field": "price", "op": "between", "value": [1000, 50000]},
            {"field": "in_stock", "op": "eq", "value": true}
        ],
        "or": [
            {"field": "name", "op": "contains", "value": "iphone"},
            {"field": "description", "op": "contains", "value": "смартфон"}
        ]
    },
    "sort": [
        {"field": "price", "order": "asc"},
        {"field": "id", "order": "asc"}
    ],
    "pagination": {
        "limit": 20,
        "cursor": "eyJpZCI6MTIzfQ=="
    }
}

Частые ошибки

Ошибка 1: Отсутствие фильтрации по умолчанию

GET /users

Возвращает всех пользователей. На 10 миллионах записей убьёт сервер.

Как исправить: Всегда ограничивайте результат. Пагинация по умолчанию (limit=20). Или требуйте явные фильтры.

Ошибка 2: Сортировка по неиндексированным полям

GET /users?sort=-bio

Сортировка по текстовому полю bio без индекса — медленно.

Как исправить: Индексировать поля, по которым разрешена сортировка. Документировать, какие поля можно использовать.

Ошибка 3: Нестабильная сортировка

GET /users?sort=created_at

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

Как исправить: Добавлять уникальное поле (id) в сортировку.

Ошибка 4: Слишком сложные фильтры в query string

GET /products?filter=((category=electronics;price=gt=1000),(category=books;price=gt=500))

Как исправить: POST /products/search с JSON телом.

Ошибка 5: Фильтрация по подстроке с ведущим wildcard

GET /products?name_contains=phone

Если нет индекса для поиска по подстроке, запрос медленный.

Как исправить: Использовать полнотекстовый поиск (Elasticsearch, PostgreSQL GIN).

Ошибка 6: Несогласованность имён параметров

GET /users?status=active        # поле = status
GET /products?product_status=active  # поле = product_status

Как исправить: Единое правило именования.

Рекомендации по проектированию

Именование параметров

ЧтоРекомендацияПример
Равенствоfieldstatus=active
Большеfield_gtprice_gt=1000
Больше или равноfield_gteage_gte=18
Меньшеfield_ltprice_lt=5000
Меньше или равноfield_lteage_lte=65
Диапазонfield_betweenprice_between=1000,5000
Списокfield_instatus_in=active,pending
Содержитfield_containsname_contains=иван
Начинается сfield_starts_withname_starts_with=a
NULLfield_nullphone_null=true

Сортировка

СтильПример
Минус для DESCsort=-created_at
Поле .descsort=created_at.desc
Отдельные параметрыsort_by=created_at&sort_dir=desc

Пагинация

СтильПример
Offset/Limitlimit=20&offset=40
Page/Limitpage=3&limit=20
Cursorlimit=20&cursor=123

Резюме для системного аналитика

  1. Фильтрация ограничивает набор данных по условиям. Без неё клиент получает все записи, что неприемлемо для больших объёмов.

  2. Сортировка упорядочивает результат. Без неё порядок записей непредсказуем, особенно при пагинации.

  3. Способы передачи фильтров: query параметры (простые условия), тело POST запроса (сложные условия), путь URL (предопределённые фильтры).

  4. Операторы фильтрации: равенство (field=value), сравнение (field_gt, field_lt), диапазон (field_between), список (field_in), частичное совпадение (field_contains), проверка NULL (field_null).

  5. Сортировка поддерживает одно или несколько полей, возрастание (ASC) и убывание (DESC). Для стабильности всегда добавляйте уникальное поле (id).

  6. Комбинация: фильтрация → сортировка → пагинация. Сначала отбираем нужные записи, потом сортируем, потом разбиваем на страницы.

Проверка знаний

Вопрос 1 из 4
Зачем в API нужны фильтрация и сортировка?
Какой тип параметров обычно используют для фильтрации в REST API?
Почему важно осторожно проектировать фильтрацию по строкам и подстрокам?
Что важно для сортировки при постраничной выборке?

Вопросы, где были ошибки