Фильтрация и сортировка
Фильтрация — ограничение набора данных по условиям (равенство, сравнение, диапазон, список, частичное совпадение, 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 | Реже |
| Синтаксис JSON | filter={"age":{"gte":18}} | POST запрос |
| Синтаксис RSQL | filter=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==deletedJSON формат (через 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Как исправить: Единое правило именования.
Рекомендации по проектированию
Именование параметров
| Что | Рекомендация | Пример |
|---|---|---|
| Равенство | field | status=active |
| Больше | field_gt | price_gt=1000 |
| Больше или равно | field_gte | age_gte=18 |
| Меньше | field_lt | price_lt=5000 |
| Меньше или равно | field_lte | age_lte=65 |
| Диапазон | field_between | price_between=1000,5000 |
| Список | field_in | status_in=active,pending |
| Содержит | field_contains | name_contains=иван |
| Начинается с | field_starts_with | name_starts_with=a |
| NULL | field_null | phone_null=true |
Сортировка
| Стиль | Пример |
|---|---|
| Минус для DESC | sort=-created_at |
| Поле .desc | sort=created_at.desc |
| Отдельные параметры | sort_by=created_at&sort_dir=desc |
Пагинация
| Стиль | Пример |
|---|---|
| Offset/Limit | limit=20&offset=40 |
| Page/Limit | page=3&limit=20 |
| Cursor | limit=20&cursor=123 |
Резюме для системного аналитика
Фильтрация ограничивает набор данных по условиям. Без неё клиент получает все записи, что неприемлемо для больших объёмов.
Сортировка упорядочивает результат. Без неё порядок записей непредсказуем, особенно при пагинации.
Способы передачи фильтров: query параметры (простые условия), тело POST запроса (сложные условия), путь URL (предопределённые фильтры).
Операторы фильтрации: равенство (
field=value), сравнение (field_gt,field_lt), диапазон (field_between), список (field_in), частичное совпадение (field_contains), проверка NULL (field_null).Сортировка поддерживает одно или несколько полей, возрастание (ASC) и убывание (DESC). Для стабильности всегда добавляйте уникальное поле (id).
Комбинация: фильтрация → сортировка → пагинация. Сначала отбираем нужные записи, потом сортируем, потом разбиваем на страницы.