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

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 вводится вручную