#node.js #mongodb #aggregation-framework
#node.js #mongodb #агрегация-фреймворк
Вопрос:
У меня есть массив в mongodb: я хочу, чтобы из данного массива было получено максимальное devDependenciesList
значение
[{
"_id" : 0,
"repoId" : 460078,
"devDependenciesList" : [
"value1",
"value2",
"value3",
"value4"
]
},{
"_id" : 1,
"repoId" : 1232,
"devDependenciesList" : [
"value1",
"value4",
"value7",
"value93"
]
},{
"_id" : 2,
"repoId" : 5423,
"devDependenciesList" : [
"value1",
"value23",
"value3",
"value4"
]
}]
Результат должен быть :
[value1:3,value4:3,value3:2]
Ответ №1:
В основном вам нужно $unwind
содержимое массива, а затем $group
для каждого значения в качестве ключа группировки с $sum
для подсчета:
db.collection.aggregate([
{ "$unwind": "$devDependenciesList" },
{ "$group": {
"_id": "$devDependenciesList",
"count": { "$sum": 1 }
}}
])
Которое вернуло бы:
{ "_id" : "value23", "count" : 1 }
{ "_id" : "value93", "count" : 1 }
{ "_id" : "value7", "count" : 1 }
{ "_id" : "value2", "count" : 1 }
{ "_id" : "value3", "count" : 2 }
{ "_id" : "value1", "count" : 3 }
{ "_id" : "value4", "count" : 3 }
Это основные данные прямо там, но если вам действительно нужна форма «ключ / количество», вы можете сделать:
db.collection.aggregate([
{ "$unwind": "$devDependenciesList" },
{ "$group": {
"_id": "$devDependenciesList",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$group": {
"_id": null,
"items": { "$push": { "k": "$_id", "v": "$count" } }
}},
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$items" }
}}
])
Которое вернуло бы:
{
"value1" : 3,
"value4" : 3,
"value3" : 2,
"value23" : 1,
"value93" : 1,
"value7" : 1,
"value2" : 1
}
Дополнительные $group
и $push
предназначены для сбора всех результатов в один документ с массивом, названным с помощью элементов "k"
и "v"
. Вы хотите, чтобы эта форма для $arrayToObject
оператора, который используется на следующем $replaceRoot
этапе, возвращала конечный результат.
Вам нужна версия MongoDB, которая поддерживает эти последние операторы, но на самом деле у вас их нет. На самом деле это наиболее эффективно выполняется в клиентском коде. Например, с помощью JavaScript в оболочке:
db.collection.aggregate([
{ "$unwind": "$devDependenciesList" },
{ "$group": {
"_id": "$devDependenciesList",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } }
]).toArray().reduce((o,e) => Object.assign(o, { [e._id]: e.count }),{})
И это приводит к тем же результатам, что и выше.
И, конечно, если вы хотите исключить все единичные результаты или что-то подобное, просто добавьте $match
после $group
:
db.collection.aggregate([
{ "$unwind": "$devDependenciesList" },
{ "$group": {
"_id": "$devDependenciesList",
"count": { "$sum": 1 }
}},
{ "$match": { "count": { "$gt": 1 } } },
{ "$sort": { "count": -1 } }
]).toArray().reduce((o,e) => Object.assign(o, { [e._id]: e.count }),{})
Или с использованием встроенного драйвера узла, который был бы чем-то вроде:
let result = (await db.collection('collection').aggregate([
{ "$unwind": "$devDependenciesList" },
{ "$group": {
"_id": "$devDependenciesList",
"count": { "$sum": 1 }
}},
{ "$match": { "count": { "$gt": 1 } } },
{ "$sort": { "count": -1 } }
]).toArray()).reduce((o,{ _id, count }) => ({ ...o, [_id]: count }),{})
Учитывая использование async/await
при возврате фактического массива и использование функций ES6, таких как распространение объектов и деструктурирование.
Что, конечно, просто:
{ "value1" : 3, "value4" : 3, "value3" : 2 }
Просто для справки, вот полностью воспроизводимый список:
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const opts = { useNewUrlParser: true };
const data = [
{
"_id" : 0,
"repoId" : 460078,
"devDependenciesList" : [
"value1",
"value2",
"value3",
"value4"
]
},{
"_id" : 1,
"repoId" : 1232,
"devDependenciesList" : [
"value1",
"value4",
"value7",
"value93"
]
},{
"_id" : 2,
"repoId" : 5423,
"devDependenciesList" : [
"value1",
"value23",
"value3",
"value4"
]
}
];
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
let client;
try {
client = await MongoClient.connect(uri, opts);
const db = client.db('test');
// Clean data
await db.collection('collection').deleteMany();
// Insert data
await db.collection('collection').insertMany(data);
let result = (await db.collection('collection').aggregate([
{ "$unwind": "$devDependenciesList" },
{ "$group": {
"_id": "$devDependenciesList",
"count": { "$sum": 1 }
}},
{ "$match": { "count": { "$gt": 1 } } },
{ "$sort": { "count": -1 } }
]).toArray()).reduce((o, { _id, count }) => ({ ...o, [_id]: count }),{});
log(result);
let sample = await db.collection('collection').aggregate([
{ "$unwind": "$devDependenciesList" },
{ "$sortByCount": "$devDependenciesList" },
],{ "explain": true }).toArray();
log(sample);
} catch(e) {
console.error(e);
} finally {
if (client)
client.close();
}
})()
И вывод, показывающий ожидаемый результат и вывод «explain», чтобы показать, что $sortByCount
это не «реальный» этап агрегирования, а просто более короткий способ ввода данных, который существовал еще в MongoDB 2.2:
{
"value1": 3,
"value4": 3,
"value3": 2
}
[
{
"stages": [
{
"$cursor": {
"query": {},
"fields": {
"devDependenciesList": 1,
"_id": 0
},
"queryPlanner": {
"plannerVersion": 1,
"namespace": "test.collection",
"indexFilterSet": false,
"parsedQuery": {},
"winningPlan": {
"stage": "COLLSCAN",
"direction": "forward"
},
"rejectedPlans": []
}
}
},
{
"$unwind": {
"path": "$devDependenciesList"
}
},
{
"$group": {
"_id": "$devDependenciesList",
"count": {
"$sum": {
"$const": 1
}
}
}
},
{
"$sort": {
"sortKey": {
"count": -1
}
}
}
],
"ok": 1,
"operationTime": "6674186995377373190",
"$clusterTime": {
"clusterTime": "6674186995377373190",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
]
Комментарии:
1. Как использовать это с node js?? Имею в виду, как получить его значение обратного вызова??
2. @SaurabhAgrawal Выше приведен пример. Все методы встроенного драйвера либо возвращают обещание, либо вы можете использовать обратный вызов. Но в остальном все идентично.
3. @SaurabhAgrawal Извините. Просто «произвольный ввод» здесь без проверки синтаксиса. Теперь исправлено утверждение.
4. Если вы хотите уменьшить совокупный запрос, вы можете вместо этого использовать $sortByCount, смотрите Запрос в решении ниже. Это будет служить той же цели.
5. @VikashSingh Все, что он делает, это «расширяется» на этапы
$group
и$sort
, как показано здесь. На самом деле это просто «помощник», и вы можете увидеть фактическое расширение, используя"explain"
опцию с aggregate. Кроме того, поскольку это было введено в MongoDB 3.4, это опровергает основную мысль того, что я говорил, в том смысле, что вы можете сделать это прямо в MongoDB 2.2 без использования «специальных операторов».
Ответ №2:
Пожалуйста, попробуйте использовать $sortByCount
и $unwind
, как показано ниже :
db.getCollection("test").aggregate([
{
$unwind: "$devDependenciesList"
},
{
$sortByCount: "$devDependenciesList"
}
]).map((obj)=>{return {[obj._id]:obj.count}})
Это простое и короткое решение, которое я смог придумать.