MongoDB使用条件求和进行聚合

col*_*ore 4 mongodb mongodb-query aggregation-framework

我必须做一个特殊的mongoDB查询.

我有一个包含以下文档的集合:

{
    "_id" : ObjectId("53c7fd86d624b06abc76e8f6"),
    "works" : [ 
        {
            "code" : "A001",
            "name" : "Cambiar bombilla",
            "orderId" : "53c7fd86d624b06abc76e8f6",
            "price" : 1400,
            "ID" : 1,
            "lazyLoaded" : true,
            "status" : 0,
            "Date" : ISODate("2014-07-21T10:31:55.063Z"),
            "TechnicianId" : "538efd918163b19307c59e8e",
            "_id" : ObjectId("53ccec1bf2bf4d5952b2f205")
        }, 
        {
            "code" : "A005",
            "name" : "Cambiar bombilla 5",
            "price" : 1050,
            "type" : "Bombillas",
            "TechnicianId" : "5383577a994be8b9a9e3f01e",
            "_id" : ObjectId("53ccfdbdf2bf4d5952b2f206")
        }, 
        {
            "code" : "A004",
            "name" : "Cambiar bombilla 4",
            "price" : 1010,
            "type" : "Bombillas",
            "TechnicianId" : "5383577a994be8b9a9e3f01e",
            "date" : "2014-07-21T11:50:52.702Z",
            "orderId" : "53c7fd86d624b06abc76e8f6",
            "_id" : ObjectId("53ccfe9c109c100000ad688a")
        }, 
        {
            "code" : "A002",
            "name" : "Cambiar bombilla 2",
            "price" : 1030,
            "type" : "Bombillas",
            "TechnicianId" : "5383577a994be8b9a9e3f01e",
            "date" : "2014-07-21T11:57:37.065Z",
            "orderId" : "53c7fd86d624b06abc76e8f6",
            "_id" : ObjectId("53cd0036109c100000ad688b")
        }, 
        {
            "code" : "A003",
            "name" : "Cambiar bombilla 3",
            "price" : 1050,
            "type" : "Bombillas",
            "TechnicianId" : "5383577a994be8b9a9e3f01e",
            "date" : "2014-07-21T11:59:35.586Z",
            "orderId" : "53c7fd86d624b06abc76e8f6",
            "_id" : ObjectId("53cd00a7109c100000ad688c")
        }
    ],
    "Items" : [ 
        {
            "_id" : "534ba71f394835a7e51dd938",
            "total":50
            "qty" : 2
        }, 
        {
            "_id" : "534664b081362062015d1b77",
            "qty" : 2,
            "total":30
        }
    ]}
Run Code Online (Sandbox Code Playgroud)

现在,我想查询以获得TechnicianId = 5383577a994be8b9a9e3f01e的作品,这些作品的"价格"总和扣除items.total的总和,这些文件扣除项目总和的总工作量.总.

对于这个例子,我想要这样的东西:

{
    "result" : [ 
        {
            "_id" : ObjectId("53c7fd86d624b06abc76e8f6"),
            "works.totalsumfortech" : 1050+1010+1030+1050 - (50+30),//Sum of works of this technicianId deducting the items.total qty
            "works.total":1400+1050+1010+1030+1050-(50+30)//Summ of all works of document deducting the items.total qty
        }
    ],
    "ok" : 1
}
Run Code Online (Sandbox Code Playgroud)

这是我当前的查询,但我没有得到预期的结果..

db.orders.aggregate([
        { "$match": {
            "$and": [
                {"OrderState": {$in:['Review','Archived']}},
                {'works.TechnicianId':'5383577a994be8b9a9e3f01e'}
            ]
        }},
        {$group: {
         _id: "$_id",
         'total': {$subtract:[{$sum: { $cond: [ { $eq: [ "$works.TechnicianId", "5383577a994be8b9a9e3f01e" ] } , 2, 1 ]},{$sum: '$Items.total'}]
         'total': {$subtract:[{$sum: '$works.price'},{$sum: '$Items.total'}]
         }
         }]);
Run Code Online (Sandbox Code Playgroud)

Nei*_*unn 12

现代

使用现代版本的MongoDB,这实际上要容易得多.在特定情况下,文档之间没有实际的"聚合",尽管返回的数据和要应用的"文档内的聚合"显着减少.

现代拿到这个允许这种不诉诸从阵列中的数据重塑和选择$unwind,并$group以流程:

db.getCollection('orders').aggregate([
  { "$match": {
    //"OrderState": { "$in":["Review","Archived"]},
    "works.TechnicianId":"5383577a994be8b9a9e3f01e"
  }},
  { "$project": {
    "works": {
      "$let": {
        "vars": {
          "techTotal": {
            "$sum": {
              "$map": {
                "input": { 
                  "$filter": {
                    "input": "$works",
                    "cond": {
                      "$eq": [
                        "$$this.TechnicianId",
                        "5383577a994be8b9a9e3f01e"
                      ]
                    }
                  }
                },
                "in": "$$this.price"
              }    
            }
          },
          "items_total": { "$sum": "$Items.total" },
          "worksTotal": { "$sum": "$works.price" }
        },
        "in": {
          "totalSumForTech": {
            "$subtract": [ "$$techTotal", "$$items_total" ]
          },
          "total": {
            "$subtract": [ "$$worksTotal", "$$items_total" ]
          }
        }
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

自最初提出以来的更改是在除了传统角色作为累加器之外,$sum在用于$project或类似的阶段上下文中时接受"数组"作为输入.因此,不是"展开"数组,而是可以{ "$sum": "$Items.total" }通过内部表示法从指定属性返回值数组,然后通过"减少"这些值$sum.这是它自身的一大改进.

其他改进是$map$filter.应用后者以便仅将数组的匹配条目返回到给定条件,并且对于前者允许"重新整形"数组内容.两者都是其他编程语言中用于处理数组的常用方法,并且在这里具有基本相同的功能.

这意味着您可以根据需要"price"从与"works"技术人员匹配的数组中提取值,然后$sum以与前面所述相同的方式将这些值用作"值数组".

另一个补充是$let,它允许使用"变量"声明块以便在该块中使用.在这种情况下,我们可以从数组中计算这些"总计",然后应用于$subtract计算值以获得最终结果.

与早期版本相比,您可以在不分离聚合管道阶段的情况下实现此优势.当然$$items_total可以在这里使用,而不是重复完整的语句来计算.此外,计算的一般分离使得最终输出块更容易阅读.所以它实际上只是"使用变量",就像你在常规编程中一样.

这里的巨大收益是,这变得简单$match而且$project,而不是整个管道阶段链只是为了从每个文档得到最终的计算结果.


原版的

如前所述,在MongoDB聚合中使用数组时需要使用$unwind.除非您这样做,否则聚合操作不适用于每个数组元素.

这里的其他问题是在$group管道阶段内,所有"顶级"操作都需要是组聚合操作符.$subtract不允许的事情是不允许的,因此您需要$sum在可能的情况下或在另一个管道阶段中执行这些操作:

db.orders.aggregate([

    // Match documents "and" is implied in MongoDB. Not required unless
    // against the same field
    { "$match": {
        "OrderState": { "$in":["Review","Archived"]},
        "works.TechnicianId":"5383577a994be8b9a9e3f01e"
    }},

    // Unwind Items first
    { "$unwind": "$Items" },

    // Group to get that total
    { "$group": {
        "_id": "$_id",
        "works": { "$first": "$works" },
        "items_total": { "$sum": "$Items.total" }
    }},

    // Unwind works to "de-normalize"
    { "$unwind": "$works" },

    // Group conditionally on "TechnicianId" and the full total
    { "$group": {
        "_id": "$_id",
        "techTotal": {
            "$sum": {
                "$cond": [ 
                    { "$eq": [ 
                        "$works.TechnicianId", 
                        "5383577a994be8b9a9e3f01e"
                    ]},
                    "$works.price",
                    0 
                ]
            }
        },
        "worksTotal": { "$sum": "$works.price" },
        "items_total": { "$first": "$items_total" }
    }},

    // Project to do math and other re-shaping
    { "$project": {
        "works": {
            "totalSumForTech": {
                "$subtract": [ "$techTotal", "$items_total" ]
            },
            "total": {
                "$subtract": [ "$worksTotal", "$items_total" ]
            }
        }
    }}
])
Run Code Online (Sandbox Code Playgroud)

在示例文档上(虽然我需要删除,$match因为样本中不存在该数据)结果是:

{
    "_id" : ObjectId("53c7fd86d624b06abc76e8f6"),
    "works" : {
            "totalSumForTech" : 4060,
            "total" : 5460
    }
}
Run Code Online (Sandbox Code Playgroud)