Rollup
Поле типа Rollup автоматически вычисляет агрегированное значение из связанных записей.
Основные характеристики
- Тип данных: Число, массив или другой тип (зависит от функции)
- Источник: Множество связанных записей
- Функции: COUNT, SUM, AVG, MIN, MAX, ARRAY и др.
- Режим: Только для чтения
- Обновление: Автоматическое при изменении связанных записей
Как работает Rollup
Rollup позволяет агрегировать данные из множества связанных записей.
Клиент → Заказы → Сумма всех заказов
Вместо того чтобы вручную суммировать заказы, Rollup автоматически вычисляет общую сумму.
Настройки поля
При создании поля типа "Rollup" доступны следующие настройки:
- Название поля: Отображаемое имя поля
- Системное имя: Уникальный идентификатор для API
- Описание: Подсказка для пользователей
- Поле связи: Поле типа Relation (обычно One-to-Many)
- Поле для агрегации: Поле из связанных записей
- Функция агрегации: COUNT, SUM, AVG, MIN, MAX, ARRAY и др.
- Фильтр: Условие для фильтрации связанных записей
Функции агрегации
COUNT (Подсчет)
Подсчитывает количество связанных записей.
{
"name": "orders_count",
"type": "rollup",
"relationField": "orders",
"function": "COUNT"
}
SUM (Сумма)
Суммирует значения поля из связанных записей.
{
"name": "total_revenue",
"type": "rollup",
"relationField": "orders",
"rollupField": "total",
"function": "SUM"
}
AVG (Среднее)
Вычисляет среднее значение.
{
"name": "average_order_value",
"type": "rollup",
"relationField": "orders",
"rollupField": "total",
"function": "AVG"
}
MIN (Минимум)
Находит минимальное значение.
{
"name": "min_order_value",
"type": "rollup",
"relationField": "orders",
"rollupField": "total",
"function": "MIN"
}
MAX (Максимум)
Находит максимальное значение.
{
"name": "max_order_value",
"type": "rollup",
"relationField": "orders",
"rollupField": "total",
"function": "MAX"
}
ARRAY (Массив)
Собирает все значения в массив.
{
"name": "order_numbers",
"type": "rollup",
"relationField": "orders",
"rollupField": "order_number",
"function": "ARRAY"
}
Примеры использования
Количество заказов клиента
{
"name": "orders_count",
"type": "rollup",
"relationField": "orders",
"function": "COUNT"
}
Общая сумма заказов
{
"name": "total_spent",
"type": "rollup",
"relationField": "orders",
"rollupField": "total",
"function": "SUM"
}
Средний чек
{
"name": "average_order",
"type": "rollup",
"relationField": "orders",
"rollupField": "total",
"function": "AVG"
}
Количество задач в проекте
{
"name": "tasks_count",
"type": "rollup",
"relationField": "tasks",
"function": "COUNT"
}
Количество завершенных задач
{
"name": "completed_tasks_count",
"type": "rollup",
"relationField": "tasks",
"function": "COUNT",
"filter": {
"status": "Завершена"
}
}
Работа через API
Получение значения Rollup
const customer = await emd.database.collection('customers').get(customerId);
console.log(customer.orders_count); // 15
console.log(customer.total_spent); // 125000
console.log(customer.average_order); // 8333.33
Поиск по Rollup полю
// Клиенты с более чем 10 заказами
const vipCustomers = await emd.database.collection('customers').find({
orders_count: { $gt: 10 }
});
// Клиенты, потратившие более 100000
const highSpenders = await emd.database.collection('customers').find({
total_spent: { $gte: 100000 }
});
Сортировка по Rollup полю
// Топ клиентов по сумме заказов
const topCustomers = await emd.database.collection('customers')
.find()
.sort({ total_spent: -1 })
.limit(10);
Фильтрация связанных записей
Подсчет только оплаченных заказов
{
"name": "paid_orders_count",
"type": "rollup",
"relationField": "orders",
"function": "COUNT",
"filter": {
"status": "Оплачен"
}
}
Сумма завершенных заказов
{
"name": "completed_orders_total",
"type": "rollup",
"relationField": "orders",
"rollupField": "total",
"function": "SUM",
"filter": {
"status": "Завершен"
}
}
Количество активных задач
{
"name": "active_tasks_count",
"type": "rollup",
"relationField": "tasks",
"function": "COUNT",
"filter": {
"status": { "$in": ["Новая", "В работе"] }
}
}
Сложные примеры
Статистика проекта
// В коллекции projects
[
{
"name": "total_tasks",
"type": "rollup",
"relationField": "tasks",
"function": "COUNT"
},
{
"name": "completed_tasks",
"type": "rollup",
"relationField": "tasks",
"function": "COUNT",
"filter": { "status": "Завершена" }
},
{
"name": "in_progress_tasks",
"type": "rollup",
"relationField": "tasks",
"function": "COUNT",
"filter": { "status": "В работе" }
}
]
Затем можно создать Formula для процента выполнения:
{
"name": "completion_percent",
"type": "formula",
"formula": "record.total_tasks > 0 ? (record.completed_tasks / record.total_tasks) * 100 : 0",
"resultType": "number"
}
Аналитика клиента
// В коллекции customers
[
{
"name": "orders_count",
"type": "rollup",
"relationField": "orders",
"function": "COUNT"
},
{
"name": "total_spent",
"type": "rollup",
"relationField": "orders",
"rollupField": "total",
"function": "SUM"
},
{
"name": "average_order",
"type": "rollup",
"relationField": "orders",
"rollupField": "total",
"function": "AVG"
},
{
"name": "last_order_date",
"type": "rollup",
"relationField": "orders",
"rollupField": "created_at",
"function": "MAX"
}
]
Комбинация с формулами
Средний рейтинг товара
// Rollup
{
"name": "total_rating",
"type": "rollup",
"relationField": "reviews",
"rollupField": "rating",
"function": "SUM"
},
{
"name": "reviews_count",
"type": "rollup",
"relationField": "reviews",
"function": "COUNT"
}
// Formula
{
"name": "average_rating",
"type": "formula",
"formula": "record.reviews_count > 0 ? record.total_rating / record.reviews_count : 0",
"resultType": "number"
}
Прогресс выполнения
// Rollup
{
"name": "total_tasks",
"type": "rollup",
"relationField": "tasks",
"function": "COUNT"
},
{
"name": "completed_tasks",
"type": "rollup",
"relationField": "tasks",
"function": "COUNT",
"filter": { "status": "Завершена" }
}
// Formula
{
"name": "progress",
"type": "formula",
"formula": "record.total_tasks > 0 ? Math.round((record.completed_tasks / record.total_tasks) * 100) : 0",
"resultType": "number"
}
Обновление Rollup полей
Rollup поля обновляются автоматически при:
- Создании связанной записи
- Обновлении связанной записи
- Удалении связанной записи
// Создать новый заказ
await emd.database.collection('orders').create({
customer: customerId,
total: 5000
});
// Rollup поля клиента обновятся автоматически
const customer = await emd.database.collection('customers').get(customerId);
console.log(customer.orders_count); // Увеличилось на 1
console.log(customer.total_spent); // Увеличилось на 5000
Производительность
Оптимизация
- Rollup поля кешируются и обновляются асинхронно
- Не влияют на скорость чтения
- Могут незначительно замедлить запись связанных записей
Индексация
// Создать индекс для Rollup поля
await emd.database.collection('customers').createIndex({
total_spent: -1
});
Типичные сценарии
E-commerce: Статистика клиента
const customer = await emd.database.collection('customers').get(customerId);
console.log(`Заказов: ${customer.orders_count}`);
console.log(`Потрачено: ${customer.total_spent} ₽`);
console.log(`Средний чек: ${customer.average_order} ₽`);
console.log(`Последний заказ: ${customer.last_order_date}`);
Управление проектами: Прогресс проекта
const project = await emd.database.collection('projects').get(projectId);
console.log(`Всего задач: ${project.total_tasks}`);
console.log(`Завершено: ${project.completed_tasks}`);
console.log(`В работе: ${project.in_progress_tasks}`);
console.log(`Прогресс: ${project.completion_percent}%`);
CRM: Активность менеджера
const manager = await emd.database.collection('users').get(managerId);
console.log(`Клиентов: ${manager.customers_count}`);
console.log(`Сделок: ${manager.deals_count}`);
console.log(`Сумма сделок: ${manager.deals_total} ₽`);
console.log(`Конверсия: ${manager.conversion_rate}%`);
Отображение в интерфейсе
Карточка клиента
const customer = await emd.database.collection('customers').get(customerId);
const statsHTML = `
<div class="customer-stats">
<div class="stat">
<div class="label">Заказов</div>
<div class="value">${customer.orders_count}</div>
</div>
<div class="stat">
<div class="label">Потрачено</div>
<div class="value">${customer.total_spent.toLocaleString()} ₽</div>
</div>
<div class="stat">
<div class="label">Средний чек</div>
<div class="value">${customer.average_order.toLocaleString()} ₽</div>
</div>
</div>
`;
Рекомендации
- Используйте Rollup для агрегации данных из связанных записей
- Создавайте индексы для Rollup полей, по которым часто ищете или сортируете
- Комбинируйте Rollup с Formula для сложных вычислений
- Используйте фильтры для подсчета только нужных записей
- Документируйте логику Rollup полей
Ограничения
- Работает только с полями типа Relation
- Нельзя делать Rollup из Rollup
- Максимальная глубина вложенности: 1 уровень
- Rollup поля нельзя редактировать напрямую
Отличие от других типов
- Rollup vs Lookup: Rollup агрегирует множество записей, Lookup получает одно значение
- Rollup vs Formula: Rollup для агрегации связанных записей, Formula для вычислений в текущей записи
- Rollup vs Number: Rollup вычисляется автоматически из связанных записей, Number вводится вручную