Как повторно настроить максимальное вхождение значения внутри массива MongoDB

#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}})  

Это простое и короткое решение, которое я смог придумать.