来自Foreign Collection $查找结果的项目特定字段

时间:2017-11-02 04:27:36

标签: javascript node.js mongodb express aggregation-framework

我正在编写聚合以获取带有本地集合的外部集合数据。

db.getCollection('orders').aggregate([
                    {
                        $match: {
                            status: "UNASSIGNED",
                            serviceLocationId: "83177"
                        }
                    }, {
                        $lookup: {
                            from: "servicelocations",
                            localField: "serviceLocationId",
                            foreignField: "serviceLocationId",
                            as: "locations"
                        }
                    }, {
                        $unwind: "$locations"
                    }])

我得到了:

{
    "_id" : ObjectId("59d32b5c360198e441b67545"),
    "accountId" : 1.0,
    "orderId" : "AQ137O1701240",
    "serviceLocationId" : "83177",
    "orderDate" : "2017-09-18T18:29:00.000Z",
    "description" : "AQ137O1701240",
    "serviceType" : "Delivery",
    "orderSource" : "Import",
    "takenBy" : "KARIM",
    "plannedDeliveryDate" : ISODate("2017-10-09T00:00:00.000Z"),
    "plannedDeliveryTime" : "",
    "actualDeliveryDate" : "",
    "actualDeliveryTime" : "",
    "deliveredBy" : "",
    "size1" : 25.0,
    "size2" : 464.0,
    "size3" : 46.0,
    "jobPriority" : 1.0,
    "cancelReason" : "",
    "cancelDate" : "",
    "cancelBy" : "",
    "reasonCode" : "",
    "reasonText" : "",
    "status" : "UNASSIGNED",
    "lineItems" : [ 
        {
            "ItemId" : "MMGW001",
            "size1" : 25.0,
            "size2" : 464.38,
            "size3" : 46.875
        }
    ],
    "locations" : {
        "_id" : ObjectId("59ce18e172dbf6926093e189"),
        "accountId" : 1.0,
        "serviceLocationId" : "83177",
        "regionId" : "1",
        "zoneId" : "DXBZONE1",
        "description" : "EXPRESS BLUE MART SUPERMARKET",
        "locationPriority" : 1.0,
        "accountTypeId" : 1.0,
        "locationType" : "SERVICELOCATION",
        "location" : {
            "makani" : "",
            "lng" : 55.179042,
            "lat" : 25.098741
        },
        "deliveryDays" : "MTWRFSU",
        "serviceTimeTypeId" : "1",
        "timeWindow" : {
            "timeWindowTypeId" : "1"
        },
        "address1" : "",
        "address2" : "",
        "phone" : "",
        "city" : "",
        "county" : "",
        "state" : "",
        "country" : "",
        "zipcode" : "",
        "imageUrl" : "",
        "contact" : {
            "name" : "",
            "email" : ""
        },
        "status" : "ACTIVE",
        "createdBy" : "",
        "updatedBy" : "",
        "updateDate" : ""
    }
}

但我需要:

{
    "_id" : ObjectId("59d32b5c360198e441b67545"),
    "accountId" : 1.0,
    "orderId" : "AQ137O1701240",
    "serviceLocationId" : "83177",
    "orderDate" : "2017-09-18T18:29:00.000Z",
    "description" : "AQ137O1701240",
    "serviceType" : "Delivery",
    "orderSource" : "Import",
    "takenBy" : "KARIM",
    "plannedDeliveryDate" : ISODate("2017-10-09T00:00:00.000Z"),
    "plannedDeliveryTime" : "",
    "actualDeliveryDate" : "",
    "actualDeliveryTime" : "",
    "deliveredBy" : "",
    "size1" : 25.0,
    "size2" : 464.0,
    "size3" : 46.0,
    "jobPriority" : 1.0,
    "cancelReason" : "",
    "cancelDate" : "",
    "cancelBy" : "",
    "reasonCode" : "",
    "reasonText" : "",
    "status" : "UNASSIGNED",
    "lineItems" : [ 
        {
            "ItemId" : "MMGW001",
            "size1" : 25.0,
            "size2" : 464.38,
            "size3" : 46.875
        }
    ],
    "locations" : {
            "lng" : 55.179042,
            "lat" : 25.098741
        }
}

1 个答案:

答案 0 :(得分:2)

MongoDB小于3.4.4

基本上,使用$project作为最后阶段,然后选择所需的所有特定字段。不幸的是$addFields已经出局了,因为它实际上会将子键与现有子键合并。所以看似简单:

{ "$addFields": {
  "locations": {
    "lng": "$locations.location.lng",
    "lat": "$locations.location.lat"    
  }
}}

只提供"locations"下的所有现有内容以及新定义的密钥。当然,除非您$unwind之后直接$lookup,否则如果这不会导致BSON limit to be exceeded,您可以这样做。 (这称为$lookup + $unwind合并)

然后我们可以将$addFields$map一起使用,因为我们可以简单地“重新映射”数组:

   { "$addFields": {
     "locations": {
       "$map": {
         "input": "$locations",
         "as": "l",
         "in": {
           "lng": "$$l.location.lng",
           "lat": "$$l.location.lat"
         }
       } 
     }  
   }},
   { "$unwind": "$locations" }

然后$unwind如果您仍然需要在重新映射之后。

所以$project就是:

  { "$project": {
    "accountId" : 1,
    "orderId" : 1,
    "serviceLocationId" : 1,
    "orderDate" : 1,
    "description" : 1,
    "serviceType" : 1,
    "orderSource" : 1,
    "takenBy" : 1,
    "plannedDeliveryDate" : 1,
    "plannedDeliveryTime" : 1,
    "actualDeliveryDate" : 1,
    "actualDeliveryTime" : 1,
    "deliveredBy" : 1,
    "size1" : 1,
    "size2" : 1,
    "size3" : 1,
    "jobPriority" : 1,
    "cancelReason" : 1,
    "cancelDate" : 1,
    "cancelBy" : 1,
    "reasonCode" : 1,
    "reasonText" : 1,
    "status" : 1,
    "lineItems" : 1,
    "locations" : {
      "lng": "$locations.location.lng",
      "lat": "$locations.location.lat"    
    }
  }}

简单而又啰嗦。

MongoDB 3.4.4或更高版本

如果您拥有$objectToArray$arrayToObject的MongoDB 3.4.4或更高版本,那么您可能会更喜欢它:

  { "$replaceRoot": {
    "newRoot": {  
      "$arrayToObject": {
        "$concatArrays": [
          { "$filter": {
            "input": { "$objectToArray": "$$ROOT" },
            "cond": { "$ne": [ "$$this.k", "locations" ] }
          }},
          { "$objectToArray": {
            "locations": {
              "lng": "$locations.location.lng",
              "lat": "$locations.location.lat"    
            }
          }}
        ]
      }
    }
  }}    

基本上从$$ROOT获取整个文档中的所有字段,将其转换为数组格式。然后,我们通过“密钥名称”$filter "location"字段,使用新的"location"密钥$concatArrays将其再次转换为数组。

最后当然$arrayToObject接受并转换回一个对象,该对象作为最终输出提供给$replaceRoot newRoot

因此,在$addFields $unwind之后使用$lookup之外的其中任何一个都会为您提供正确的结果:

/* 1 */
{
    "_id" : ObjectId("59d32b5c360198e441b67545"),
    "accountId" : 1.0,
    "orderId" : "AQ137O1701240",
    "serviceLocationId" : "83177",
    "orderDate" : "2017-09-18T18:29:00.000Z",
    "description" : "AQ137O1701240",
    "serviceType" : "Delivery",
    "orderSource" : "Import",
    "takenBy" : "KARIM",
    "plannedDeliveryDate" : ISODate("2017-10-09T00:00:00.000Z"),
    "plannedDeliveryTime" : "",
    "actualDeliveryDate" : "",
    "actualDeliveryTime" : "",
    "deliveredBy" : "",
    "size1" : 25.0,
    "size2" : 464.0,
    "size3" : 46.0,
    "jobPriority" : 1.0,
    "cancelReason" : "",
    "cancelDate" : "",
    "cancelBy" : "",
    "reasonCode" : "",
    "reasonText" : "",
    "status" : "UNASSIGNED",
    "lineItems" : [ 
        {
            "ItemId" : "MMGW001",
            "size1" : 25.0,
            "size2" : 464.38,
            "size3" : 46.875
        }
    ],
    "locations" : {
        "lng" : 55.179042,
        "lat" : 25.098741
    }
}

MongoDB 3.6及更高版本

作为一个预览,$expr对MongoDB 3.6进行了更具表现力的改造。所以你实际上可以明确地说明字段返回:

{ "$lookup": {
  "from": "servicelocations",
  "let": { "serviceLocationId": "$serviceLocationId" },
  "pipeline": [
    { "$match": { "$expr": { "$eq": [ "serviceLocationId", "$$serviceLocationId" ] } }},
    { "$project": {
      "_id": 0,
      "lng": "$location.lng",
      "lat": "$location.lat"
    }}
  ],
  "as": "locations"
}}

实际发布时更方便一点。这实际上使用$match而不是localFieldforeignField来定义子管道的$project阶段中的“加入”条件。然后,您只需$lookup要返回的字段,然后进入{{3}}定位的数组。

展望未来,这是您想要采取的一般方法,因为它限制了实际返回的内容。