source

mongodb 그룹 값, 여러 필드별)

itover 2023. 3. 8. 21:07
반응형

mongodb 그룹 값, 여러 필드별)

예를 들어 다음과 같은 문서가 있습니다.

{
  "addr": "address1",
  "book": "book1"
},
{
  "addr": "address2",
  "book": "book1"
},
{
  "addr": "address1",
  "book": "book5"
},
{
  "addr": "address3",
  "book": "book9"
},
{
  "addr": "address2",
  "book": "book5"
},
{
  "addr": "address2",
  "book": "book1"
},
{
  "addr": "address1",
  "book": "book1"
},
{
  "addr": "address15",
  "book": "book1"
},
{
  "addr": "address9",
  "book": "book99"
},
{
  "addr": "address90",
  "book": "book33"
},
{
  "addr": "address4",
  "book": "book3"
},
{
  "addr": "address5",
  "book": "book1"
},
{
  "addr": "address77",
  "book": "book11"
},
{
  "addr": "address1",
  "book": "book1"
}

기타 등등.


주소별 상위 N개 주소와 상위 M개 장부를 설명하는 요청은 어떻게 해야 합니까?

예상되는 결과의 예:

| : 5 address1 | book_1 : 5
| book_2: 10
| || __3: 50
| | 합 65: 65

| : 10 address2 | book_1 : 10
| book_2: 10
|...
| book_M: 10
| | *10M*10


N | : 20 address N | book_1 : 20
| book_2: 20
|...
| book_M: 20
| | m: *20

TLDR의 개요

최신 MongoDB 릴리즈에서는 기본적인 집약 결과에서 벗어나면 이를 강제할 수 있습니다.'대규모' 결과를 얻으려면 각 그룹에 대해 병렬 쿼리를 실행하거나(시연 목록은 답변 끝에 있음) SERVER-9377이 해결될 때까지 기다리십시오.이것에 의해, 다음의 항목의 수에 「제한」이 주어집니다.$push배열로 이동합니다.

db.books.aggregate([
    { "$group": {
        "_id": {
            "addr": "$addr",
            "book": "$book"
        },
        "bookCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.addr",
        "books": { 
            "$push": { 
                "book": "$_id.book",
                "count": "$bookCount"
            },
        },
        "count": { "$sum": "$bookCount" }
    }},
    { "$sort": { "count": -1 } },
    { "$limit": 2 },
    { "$project": {
        "books": { "$slice": [ "$books", 2 ] },
        "count": 1
    }}
])

MongoDB 3.6 미리보기

SERVER-9377은 아직 해결되지 않았지만 이 릴리스에서는 새로운 "비상관" 옵션을 사용할 수 있습니다."pipeline""localFields" ★★★★★★★★★★★★★★★★★」"foreignFields"옵션들.그러면 다른 파이프라인 식과 함께 "셀프 조인"을 할 수 있으며, "top-n" 결과를 반환하기 위해 적용할 수 있습니다.

db.books.aggregate([
  { "$group": {
    "_id": "$addr",
    "count": { "$sum": 1 }
  }},
  { "$sort": { "count": -1 } },
  { "$limit": 2 },
  { "$lookup": {
    "from": "books",
    "let": {
      "addr": "$_id"
    },
    "pipeline": [
      { "$match": { 
        "$expr": { "$eq": [ "$addr", "$$addr"] }
      }},
      { "$group": {
        "_id": "$book",
        "count": { "$sum": 1 }
      }},
      { "$sort": { "count": -1  } },
      { "$limit": 2 }
    ],
    "as": "books"
  }}
])

는 물론 수 입니다.$expr를 사용하여 "timeout" 내의 일치하는 항목을 선택하지만, 일반적인 전제는 "pipeline 내의 항목"이며, 여기서 내부 내용은 부모로부터의 일치로 필터링할 수 있습니다.둘 다 '파이프라인'이기 때문에 각각 개별적으로 결과를 도출할 수 있습니다.

이는 병렬 쿼리를 실행하기 위한 차선책이며,가 허용되어 "서브 파이프라인" 처리에서 인덱스를 사용할 수 있는 경우에 적합합니다.즉, '한계'를 사용하지 않습니다.$push「참조된 문제로부터 요구되고 있는 것처럼, 실제로는, 보다 효율적으로 동작할 수 있는 것입니다.


오리지널 콘텐츠

가장 중요한 "N" 문제를 우연히 발견하신 것 같습니다.어떤 면에서는, 고객이 요구하는 정확한 제한은 아니지만, 문제를 해결하기가 매우 쉽습니다.

db.books.aggregate([
    { "$group": {
        "_id": {
            "addr": "$addr",
            "book": "$book"
        },
        "bookCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.addr",
        "books": { 
            "$push": { 
                "book": "$_id.book",
                "count": "$bookCount"
            },
        },
        "count": { "$sum": "$bookCount" }
    }},
    { "$sort": { "count": -1 } },
    { "$limit": 2 }
])

그러면 다음과 같은 결과가 나타납니다.

{
    "result" : [
            {
                    "_id" : "address1",
                    "books" : [
                            {
                                    "book" : "book4",
                                    "count" : 1
                            },
                            {
                                    "book" : "book5",
                                    "count" : 1
                            },
                            {
                                    "book" : "book1",
                                    "count" : 3
                            }
                    ],
                    "count" : 5
            },
            {
                    "_id" : "address2",
                    "books" : [
                            {
                                    "book" : "book5",
                                    "count" : 1
                            },
                            {
                                    "book" : "book1",
                                    "count" : 2
                            }
                    ],
                    "count" : 3
            }
    ],
    "ok" : 1
}

따라서 이는 귀하가 요구하는 것과 달리 주소 값에 대한 상위 결과를 얻을 수 있지만, 기초가 되는 "책" 선택은 필요한 양의 결과에만 국한되지 않습니다.

이것은 매우 어려운 일이지만, 매칭할 필요가 있는 항목의 수에 따라 복잡성이 증가해도 할 수 있습니다.단순하게 하기 위해 최대 2개의 매치를 유지할 수 있습니다.

db.books.aggregate([
    { "$group": {
        "_id": {
            "addr": "$addr",
            "book": "$book"
        },
        "bookCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.addr",
        "books": { 
            "$push": { 
                "book": "$_id.book",
                "count": "$bookCount"
            },
        },
        "count": { "$sum": "$bookCount" }
    }},
    { "$sort": { "count": -1 } },
    { "$limit": 2 },
    { "$unwind": "$books" },
    { "$sort": { "count": 1, "books.count": -1 } },
    { "$group": {
        "_id": "$_id",
        "books": { "$push": "$books" },
        "count": { "$first": "$count" }
    }},
    { "$project": {
        "_id": {
            "_id": "$_id",
            "books": "$books",
            "count": "$count"
        },
        "newBooks": "$books"
    }},
    { "$unwind": "$newBooks" },
    { "$group": {
      "_id": "$_id",
      "num1": { "$first": "$newBooks" }
    }},
    { "$project": {
        "_id": "$_id",
        "newBooks": "$_id.books",
        "num1": 1
    }},
    { "$unwind": "$newBooks" },
    { "$project": {
        "_id": "$_id",
        "num1": 1,
        "newBooks": 1,
        "seen": { "$eq": [
            "$num1",
            "$newBooks"
        ]}
    }},
    { "$match": { "seen": false } },
    { "$group":{
        "_id": "$_id._id",
        "num1": { "$first": "$num1" },
        "num2": { "$first": "$newBooks" },
        "count": { "$first": "$_id.count" }
    }},
    { "$project": {
        "num1": 1,
        "num2": 1,
        "count": 1,
        "type": { "$cond": [ 1, [true,false],0 ] }
    }},
    { "$unwind": "$type" },
    { "$project": {
        "books": { "$cond": [
            "$type",
            "$num1",
            "$num2"
        ]},
        "count": 1
    }},
    { "$group": {
        "_id": "$_id",
        "count": { "$first": "$count" },
        "books": { "$push": "$books" }
    }},
    { "$sort": { "count": -1 } }
])

이렇게 하면 실제로 상위 2개의 "주소" 항목에서 상위 2개의 "책"이 표시됩니다.

단, 첫 번째 폼에 머무르고 첫 번째 "N" 요소를 얻기 위해 반환되는 어레이 요소를 단순히 "슬라이스"합니다.


데모 코드

데모 코드는 v8.x 및 v10.x 릴리스의 현재 LTS 버전의 NodeJ에서 사용하기에 적합합니다.그건 주로...async/await구문에는 실제로 이러한 제한이 없으며 플레인 약속이나 플레인콜백 실장에도 거의 변경하지 않고 적응합니다.

index.displaces를 표시합니다.

const { MongoClient } = require('mongodb');
const fs = require('mz/fs');

const uri = 'mongodb://localhost:27017';

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {
    const client = await MongoClient.connect(uri);

    const db = client.db('bookDemo');
    const books = db.collection('books');

    let { version } = await db.command({ buildInfo: 1 });
    version = parseFloat(version.match(new RegExp(/(?:(?!-).)*/))[0]);

    // Clear and load books
    await books.deleteMany({});

    await books.insertMany(
      (await fs.readFile('books.json'))
        .toString()
        .replace(/\n$/,"")
        .split("\n")
        .map(JSON.parse)
    );

    if ( version >= 3.6 ) {

    // Non-correlated pipeline with limits
      let result = await books.aggregate([
        { "$group": {
          "_id": "$addr",
          "count": { "$sum": 1 }
        }},
        { "$sort": { "count": -1 } },
        { "$limit": 2 },
        { "$lookup": {
          "from": "books",
          "as": "books",
          "let": { "addr": "$_id" },
          "pipeline": [
            { "$match": {
              "$expr": { "$eq": [ "$addr", "$$addr" ] }
            }},
            { "$group": {
              "_id": "$book",
              "count": { "$sum": 1 },
            }},
            { "$sort": { "count": -1 } },
            { "$limit": 2 }
          ]
        }}
      ]).toArray();

      log({ result });
    }

    // Serial result procesing with parallel fetch

    // First get top addr items
    let topaddr = await books.aggregate([
      { "$group": {
        "_id": "$addr",
        "count": { "$sum": 1 }
      }},
      { "$sort": { "count": -1 } },
      { "$limit": 2 }
    ]).toArray();

    // Run parallel top books for each addr
    let topbooks = await Promise.all(
      topaddr.map(({ _id: addr }) =>
        books.aggregate([
          { "$match": { addr } },
          { "$group": {
            "_id": "$book",
            "count": { "$sum": 1 }
          }},
          { "$sort": { "count": -1 } },
          { "$limit": 2 }
        ]).toArray()
      )
    );

    // Merge output
    topaddr = topaddr.map((d,i) => ({ ...d, books: topbooks[i] }));
    log({ topaddr });

    client.close();

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }

})()

books.json

{ "addr": "address1",  "book": "book1"  }
{ "addr": "address2",  "book": "book1"  }
{ "addr": "address1",  "book": "book5"  }
{ "addr": "address3",  "book": "book9"  }
{ "addr": "address2",  "book": "book5"  }
{ "addr": "address2",  "book": "book1"  }
{ "addr": "address1",  "book": "book1"  }
{ "addr": "address15", "book": "book1"  }
{ "addr": "address9",  "book": "book99" }
{ "addr": "address90", "book": "book33" }
{ "addr": "address4",  "book": "book3"  }
{ "addr": "address5",  "book": "book1"  }
{ "addr": "address77", "book": "book11" }
{ "addr": "address1",  "book": "book1"  }

다음과 같은 집약 함수를 사용합니다.

[
{$group: {_id : {book : '$book',address:'$addr'}, total:{$sum :1}}},
{$project : {book : '$_id.book', address : '$_id.address', total : '$total', _id : 0}}
]

다음과 같은 결과를 얻을 수 있습니다.

        {
            "total" : 1,
            "book" : "book33",
            "address" : "address90"
        }, 
        {
            "total" : 1,
            "book" : "book5",
            "address" : "address1"
        }, 
        {
            "total" : 1,
            "book" : "book99",
            "address" : "address9"
        }, 
        {
            "total" : 1,
            "book" : "book1",
            "address" : "address5"
        }, 
        {
            "total" : 1,
            "book" : "book5",
            "address" : "address2"
        }, 
        {
            "total" : 1,
            "book" : "book3",
            "address" : "address4"
        }, 
        {
            "total" : 1,
            "book" : "book11",
            "address" : "address77"
        }, 
        {
            "total" : 1,
            "book" : "book9",
            "address" : "address3"
        }, 
        {
            "total" : 1,
            "book" : "book1",
            "address" : "address15"
        }, 
        {
            "total" : 2,
            "book" : "book1",
            "address" : "address2"
        }, 
        {
            "total" : 3,
            "book" : "book1",
            "address" : "address1"
        }

저는 당신의 예상 결과 형식을 잘 이해하지 못했기 때문에, 이것을 당신이 필요로 하는 것으로 수정해 주세요.

다음 쿼리는 원하는 응답과 동일한 결과를 제공합니다.

db.books.aggregate([
    {
        $group: {
            _id: { addresses: "$addr", books: "$book" },
            num: { $sum :1 }
        }
    },
    {
        $group: {
            _id: "$_id.addresses",
            bookCounts: { $push: { bookName: "$_id.books",count: "$num" } }
        }
    },
    {
        $project: {
            _id: 1,
            bookCounts:1,
            "totalBookAtAddress": {
                "$sum": "$bookCounts.count"
            }
        }
    }

]) 

응답은 다음과 같습니다.

/* 1 */
{
    "_id" : "address4",
    "bookCounts" : [
        {
            "bookName" : "book3",
            "count" : 1
        }
    ],
    "totalBookAtAddress" : 1
},

/* 2 */
{
    "_id" : "address90",
    "bookCounts" : [
        {
            "bookName" : "book33",
            "count" : 1
        }
    ],
    "totalBookAtAddress" : 1
},

/* 3 */
{
    "_id" : "address15",
    "bookCounts" : [
        {
            "bookName" : "book1",
            "count" : 1
        }
    ],
    "totalBookAtAddress" : 1
},

/* 4 */
{
    "_id" : "address3",
    "bookCounts" : [
        {
            "bookName" : "book9",
            "count" : 1
        }
    ],
    "totalBookAtAddress" : 1
},

/* 5 */
{
    "_id" : "address5",
    "bookCounts" : [
        {
            "bookName" : "book1",
            "count" : 1
        }
    ],
    "totalBookAtAddress" : 1
},

/* 6 */
{
    "_id" : "address1",
    "bookCounts" : [
        {
            "bookName" : "book1",
            "count" : 3
        },
        {
            "bookName" : "book5",
            "count" : 1
        }
    ],
    "totalBookAtAddress" : 4
},

/* 7 */
{
    "_id" : "address2",
    "bookCounts" : [
        {
            "bookName" : "book1",
            "count" : 2
        },
        {
            "bookName" : "book5",
            "count" : 1
        }
    ],
    "totalBookAtAddress" : 3
},

/* 8 */
{
    "_id" : "address77",
    "bookCounts" : [
        {
            "bookName" : "book11",
            "count" : 1
        }
    ],
    "totalBookAtAddress" : 1
},

/* 9 */
{
    "_id" : "address9",
    "bookCounts" : [
        {
            "bookName" : "book99",
            "count" : 1
        }
    ],
    "totalBookAtAddress" : 1
}

mongoDB version 3.6 이므로, 이것은 사용하기 쉽다.$group,$slice,$limit , , , , 입니다.$sort:

  1. $group
  2. $sort이다.
  3. $group타타에 address,$push서적 및 서적$sum주소별 합계
  4. $sort
  5. $limit는 「」입니다.topN
  6. 되어 있는 을 열음음음음음음음음음음음음음음음음음음음 합니다.topM를 사용합니다.$slice
db.collection.aggregate([
  {$group: {_id: {book: "$book",  addr: "$addr"}, count: {$sum: 1}}},
  {$sort: {"_id.addr": 1, count: -1}},
  {$group: {
      _id: "$_id.addr", totalCount: {$sum: "$count"}, 
      books: {$push: {book: "$_id.book", count: "$count"}}
    }
  },
  {$sort: {totalCount: -1}},
  {$limit: topN},
  {$set: {addr: "$_id", _id: "$$REMOVE", books: {$slice: ["$books", 0, topM]}}}
])

놀이터에서의 동작 예를 참조하십시오(v3.4).

5.에는 mongoDB '5.2'가 .topN다음과 같이 합니다.

db.collection.aggregate([
  {$group: {_id: {book: "$book",  addr: "$addr"}, count: {$sum: 1}}},
  {$group: {
      _id: "$_id.addr",
      totalCount: {$sum: "$count"},
      books: {$topN: {output: {book: "$_id.book", count: "$count"},
                      sortBy: {count: -1},
                      n: topM
      }}
  }},
  {$sort: {totalCount: -1}},
  {$limit: topN},
  {$project: {addr: "$_id", _id: 0, books: 1, totalCount: 1}}
])

놀이터에서의 동작 예를 참조하십시오(v5.2).

언급URL : https://stackoverflow.com/questions/22932364/mongodb-group-values-by-multiple-fields

반응형