Mongo: Aggregation with Pagination

Допустим у нас есть такая коллекция users в Mongo DB:

[
{
username: "john.doe",
roles: ["user"],
contacts: [
{
name: "John Doe",
email: "johndoe@gmail.com"
},
{
name: "J.D.",
email: "jd@gmail.com",
phone: "+11111111111"
}
]
},
{
username: "jack.sparrow",
roles: ["manager"],
contacts: [
{
name: "Jack Sparrow (gmail)",
email: "jack.sparrow@gmail.com",
},
{
name: "Jack Sparrow (hotmail)",
email: "jack.sparrow@hotmail.com",
}
]
}
]

И мы хотим организовать просмотр всех контактов всех пользователей в одной таблице с разбивкой по страницам (“pagination”). То есть так:

Чтобы получить все контакты можно воспользоваться aggregation с $unwind.

Еще нам нужно посчитать количество всех контактов, после чего “вырезать” только те, которые нужно показать в выбранной странице. Для того, чтобы выполнить обе эти операции одним запросом, воспользуемся чудесной пайплайн-стадией $facet. Получится так:

db.collection('users').aggregate([
{"$unwind":"$contacts"},
{"$facet":{
"meta":[
{"$count": "totalCount"}
],
"data":[
{"$sort":{'contacts.name':1,'username':1}},
{"$skip":firstRow-1},
{"$limit":page_size},
{"$replaceRoot":
{"newRoot":{
"$mergeObjects":[
{
"username":"$username",
},
"$contacts"
]
}}
},
],
}},
]);

page_size - это количество записей на странице.

firstRow - это номер первой записи на странице. Вычисляется он так:

const firstRow = (page > 0 ? ((page-1) * page_size) : 0) + 1;

Вместо $unwind могут быть другие пайплайн-стадии, которые выбирают нужные для показа данные. Основной же прием, который я хотел продемонстрировать в этом посте, - это использование $facet.

Полный пример можно посмотреть на github:

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru