【问题标题】:Aggregate Totals From Arrays从数组聚合总计
【发布时间】:2017-07-27 16:25:35
【问题描述】:

我在使用 MongoDb 对数据进行分组时遇到问题。

我有一些带有一些进出动作的物品,我想计算出带有动作量的物品的摘要,但这些动作的计算不正确。

这是我的样本数据,一个包含两只股票和一些走势的小清单......

/* 1 */
{
    "TemplateName" : "SAALottoStagionatura",
    "idStock" : 31789,
    "idWarehouse" : 191,
    "StockCode" : "71529902",   
    "Marks" : [ 
        {
            "idMark" : 20145,
            "idWarehouse" : 191,
            "idStock" : 31789,
            "ProgressivoDocumento" : 486,
            "Year" : 2016,
            "RefDate" : ISODate("2016-03-28T22:00:00.000Z"),
            "MarkedItems" : 72
        }, 
        {
            "idMark" : 20156,
            "idWarehouse" : 191,
            "idStock" : 31789,
            "ProgressivoDocumento" : 497,
            "Year" : 2016,
            "RefDate" : ISODate("2016-03-30T22:00:00.000Z"),
            "MarkedItems" : 144
        }, 
        {
            "idMark" : 23424,
            "idWarehouse" : 191,
            "idStock" : 31789,
            "ProgressivoDocumento" : 840,
            "Year" : 2016,
            "RefDate" : ISODate("2016-06-12T22:00:00.000Z"),
            "MarkedItems" : 3
        }
    ],
    "Details" : [ 
        {
            "idLSDetail" : 42781,
            "idStock" : 31789,
            "idStockOrig" : 54502,
            "StockCode" : "71529902",
            "Items" : 4532
        }
    ],   
    "MovementsOut" : [ 
        {
            "idMovementDetail" : 633,
            "idMovement" : 511,
            "MovedItems" : 3528 ,
            "idStockOrig" : null,
            "idStock" : 31789
        }
    ],
    "MovementsIn" : [ 
        {
            "idMovementDetail" : 715,
            "idMovement" : 570,
            "MovedItems" : 3528,
            "idStockOrig" : null,
            "idStock" : 33678
        }
    ]
}


/* 2 */
{
    "TemplateName" : "SAALottoStagionatura",
    "idStock" : 33678,
    "idWarehouse" : 190,
    "StockCode" : "71529902",
    "Marks" : [],
    "Details" : [ 
        {
            "idLSDetail" : 45206,
            "idStock" : 33678,
            "idStockOrig" : 56684,
            "StockCode" : "71529902",
            "Items" : 3528 

        }
    ],
   "MovementsOut" : [ 
        {
           "idMovementDetail" : 715,
            "idMovement" : 570,
            "MovedItems" : 3528,
            "idStockOrig" : null,
            "idStock" : 33678
        }
    ],
    "TrasferimentiInEntrata" : []
}

在我的查询中,我尝试对动作进行分组

db.getCollection('Test')
.aggregate(
 [
      {$match: {"idWarehouse": 191, StockCode: "71529902" } }, 
      {$unwind: "$Details"}, 
      {$unwind: "$Marks"},       
      {$unwind: "$MovementsIn"},     
      {$unwind: "$MovementsOut"},     
       {
        $group : {
           _id : {  
               StockCode: "$idStock",
               StockCode: "$StockCode" 
               },

           tot: { $sum: "$Details.Items" },
           cer: { $sum: "$Marks.MarkedItems" },
           in: { $sum: "$MovementsIn.MovedItems" },
           out: { $sum: "$MovementsOut.MovedItems" }   
        }
      } 
   ]
)

我的期望应该是这样的

{
    "_id" : {
        "StockCode" : "71529902"
    },
    "tot" : 13596,
    "cer" : 219,
    "in" : 3528,
    "out" : 7056
}

但是,我总是得到进出运动的全部总和 (10584)。我哪里错了?

【问题讨论】:

    标签: mongodb aggregation-framework


    【解决方案1】:

    实际上,在自 3.2 以来的任何现代 MongoDB 版本中,您只需使用 $sum 的“双重”调用,而根本不使用 $unwind

    db.getCollection('Test').aggregate([
      { "$group": {
        "_id": {
          "StockCode": "$StockCode"
        },
        "tot": { "$sum": { "$sum": "$Details.Items" } },
        "cer": { "$sum": { "$sum": "$Marks.MarkedItems" } },
        "in": { "$sum": { "$sum": "$MovementsIn.MovedItems" } },
        "out": { "$sum": { "$sum": "$MovementsOut.MovedItems" } }
      }}
    ])
    

    这是因为从那个版本开始,当您对数组中的元素(例如 "$Details.Items")进行注释时,投影结果是在指定路径中找到的“值数组”。第二个添加是$sum 也“对数组求和”,所以它在组中被调用到$sum 数组内容,然后$sum 作为文档之间的“累加器”。

    针对问题中的两个文档运行时返回结果:

    /* 1 */
    {
        "_id" : {
            "StockCode" : "71529902"
        },
        "tot" : 8060.0,
        "cer" : 219.0,
        "in" : 3528.0,
        "out" : 7056.0
    }
    

    在 MongoDB 2.6 等早期版本中,您可以通过将数组合并为一个来避免“笛卡尔积”,这是 $unwind 在多个数组上的结果,如果标识符和值实际上是唯一的,则可能使用 $setUnion

    db.getCollection('Test').aggregate([
      { "$project": {
        "StockCode": 1,
        "combined": { 
          "$setUnion": [
            { "$map": {
              "input": { "$ifNull": [ "$Details", [] ] },
              "as": "el",
              "in": { "id": "$idLSDetail", "k": "Details", "v": "$$el.Items" }
            }},
            { "$map": {
              "input": { "$ifNull": [ "$Marks", [] ] },
              "as": "el",
              "in": { "id": "$idMark", "k": "Marks", "v": "$$el.MarkedItems" }
            }},
            { "$map": {
              "input": { "$ifNull": [ "$MovementsIn", [] ] },
              "as": "el",
              "in": { "id": "$idMovementDetail", "k": "MoveIn", "v": "$$el.MovedItems" }
            }},  
            { "$map": {
              "input": { "$ifNull": [ "$MovementsOut", [] ] },
              "as": "el",
              "in": { "id": "$idMovementDetail", "k": "MoveOut", "v": "$$el.MovedItems" }
            }}
          ]
        }
      }},
      { "$unwind": "$combined" },
      { "$group": {
        "_id": {
          "StockCode": "$StockCode" 
        },
        "tot": {
          "$sum": {
            "$cond": {
              "if": { "$eq": [ "$combined.k", "Details" ] },
              "then": "$combined.v",
              "else": 0  
            }  
          }  
        },
        "cer": {
          "$sum": {
            "$cond": {
              "if": { "$eq": [ "$combined.k", "Marks" ] },
              "then": "$combined.v",
              "else": 0  
            }  
          }  
        },
        "in": {
          "$sum": {
            "$cond": {
              "if": { "$eq": [ "$combined.k", "MoveIn" ] },
              "then": "$combined.v",
              "else": 0  
            }  
          }  
        },
        "out": {
          "$sum": {
            "$cond": {
              "if": { "$eq": [ "$combined.k", "MoveOut" ] },
              "then": "$combined.v",
              "else": 0  
            }  
          }  
        }
      }}
    ])
    

    在旧版本中,或者实际上不可能“唯一性”的地方,您可以分别$unwind 每个数组然后$group,并重复该过程直到数组“减少”。然后你可以$group作为最终跨文档。

    但是像上面一样,您需要小心,因为并非所有文档都有所有数组,这需要处理。在上面的示例中,我们可以只提供一个“空”数组而不是 null(在最初的 $sum 不关心)。但是,一旦您对每个单独使用$unwind,如果那里没有任何内容或为空,您就会遇到问题:

     db.getCollection('Test').aggregate([
      { "$project": {
         "StockCode": 1,
         "Details": { 
           "$cond": [
              { "$eq": [{ "$size": { "$ifNull": [ "$Details", [] ] } }, 0] },
              [null],
              "$Details"
            ] 
         },
         "Marks": { 
           "$cond": [
              { "$eq": [{ "$size": { "$ifNull": [ "$Marks", [] ] } }, 0] },
              [null],
              "$Marks"
            ] 
         },
         "MovementsIn": { 
           "$cond": [
              { "$eq": [{ "$size": { "$ifNull": [ "$MovementsIn", [] ] } }, 0] },
              [null],
              "$MovementsIn"
            ] 
         },
         "MovementsOut": { 
           "$cond": [
              { "$eq": [{ "$size": { "$ifNull": [ "$MovementsOut", [] ] } }, 0] },
              [null],
              "$MovementsOut"
            ] 
         }
      }},
      { "$unwind": "$Details" },
      { "$group": {
        "_id": "$_id",
        "StockCode": { "$first": "$StockCode" },
        "tot": { "$sum": "$Details.Items" },
        "Marks": { "$first": "$Marks" },
        "MovementsIn": { "$first": "$MovementsIn" },
        "MovementsOut": { "$first": "$MovementsOut" }    
      }},
      { "$unwind": "$Marks" },
      { "$group": {
        "_id": "$_id",
        "StockCode": { "$first": "$StockCode" },
        "tot": { "$first": "$tot" },
        "cer": { "$sum": "$Marks.MarkedItems" },
        "MovementsIn": { "$first": "$MovementsIn" },
        "MovementsOut": { "$first": "$MovementsOut" }    
      }},
      { "$unwind": "$MovementsIn" },
      { "$group": {
        "_id": "$_id",
        "StockCode": { "$first": "$StockCode" },
        "tot": { "$first": "$tot" },
        "cer": { "$first": "$cer" },
        "in": { "$sum": "$MovementsIn.MovedItems" },
        "MovementsOut": { "$first": "$MovementsOut" }    
      }},
      { "$unwind": "$MovementsOut" },
      { "$group": {
        "_id": "$_id",
        "StockCode": { "$first": "$StockCode" },
        "tot": { "$first": "$tot" },
        "cer": { "$first": "$cer" },
        "in": { "$first": "$in" },
        "out": { "$sum": "$MovementsOut.MovedItems" }    
      }},
      { "$group": {
        "_id": {
          "StockCode": "$StockCode",
        },
        "tot": { "$sum": "$tot" },
        "cer": { "$sum": "$cer" },
        "in": { "$sum": "$in" },
        "out": { "$sum": "$out" }
      }}
    ])
    

    所以那里的测试需要$ifNull$size 来确定是否需要更换阵列。

    【讨论】:

    • 非常清晰完整的答案。非常感谢!
    猜你喜欢
    • 2014-07-27
    • 1970-01-01
    • 1970-01-01
    • 2019-06-14
    • 1970-01-01
    • 2013-04-17
    • 1970-01-01
    • 1970-01-01
    • 2019-10-03
    相关资源
    最近更新 更多