Node.js 聚合查询运算符,不包括具有重复键的值MongoDB

Node.js 聚合查询运算符,不包括具有重复键的值MongoDB,node.js,mongodb,express,mongoose,aggregation-framework,Node.js,Mongodb,Express,Mongoose,Aggregation Framework,我实施了一个系统,其中有两种类型的价格: 特定于确定客户的价格clientId、productId 许多客户常见的价格feeId、productId 我的模式是灵活的,即有clientId为1的价格和feeId为2的价格。但它们都必须有一个productId 只有一个价格具有相同的productId和相同的clientId var schema = new Schema({ feeId: {type: Schema.Types.ObjectId, ref: 'Fee'},

我实施了一个系统,其中有两种类型的价格:

特定于确定客户的价格clientId、productId 许多客户常见的价格feeId、productId 我的模式是灵活的,即有clientId为1的价格和feeId为2的价格。但它们都必须有一个productId

只有一个价格具有相同的productId和相同的clientId

var schema = new Schema({
    feeId: {type: Schema.Types.ObjectId, ref: 'Fee'},    
    clientId: {type: Schema.Types.ObjectId, ref: 'Client'},
    productId: {type: Schema.Types.ObjectId, ref: 'Product', required:true},
    price: {type: Number, required: true}
});
我的目标是获取特定客户和产品系列的价格,查询应返回与客户价格匹配的clientId(如果有)的价格,以及属于该系列的产品的价格(有feeId,没有clientId,匹配familyId)

现在我有这个:

  Price.aggregate([
    {
      $lookup:
        {
          'from': 'products',
          'localField': 'productId',
          'foreignField': '_id',
          'as': 'product'
        }
    },
    {
      $unwind: '$product'
    },
    {
      $match: {
        $and: [
          {
            "product.family": new mongoose.Types.ObjectId(req.params.familyId)
          },
          {
            $or: [
              {"clientId": new mongoose.Types.ObjectId(req.params.clientId)},
              {"feeId": {$exists: true, $ne: null}}
            ]
          }

        ]
      }
    },
  ])
通过该查询,我得到以下结果:

    {
  "title": "Precio",
  "body": [
    {
      "_id": "5a0974c7347eff02c784e5bd",
      "price": 8,
      "productId": "5a0970f9347eff02c784e5b1",
      "clientId": "59f350dfb8634f659680299e",
      "product": {
        "_id": "5a0970f9347eff02c784e5b1",
        "erpId": "12345",
        "name": "Entrecot Entero",
        "description": "Pieza entera",
        "weight": null,
        "photoURL": "https://okelan.s3.amazonaws.com/x14-FILETES-ENTRECOT-250gr_PREMIUM-e1491499466438-n6oowu8z2p0d877vgp4eioezbckou4u636uwvc35mo.jpg.pagespeed.ic.l38ZGRUwf6.jpg",
        "familyId": "59e721e8b8634f65967eabf4"
      },
      "client": {
        "_id": "59f350dfb8634f659680299e",
        "contactName": "Pablo 2",
        "NIF": "12345678Z",
        "email": "pablogm.grao@gmail.com",
        "password": "$2a$10$gXn.G/q4ar3wbjSyBCu0XOVud0HL5l3d.2WXNab4cCfB3uToncQLm",
        "orders": [],
        "erpId": "14123",
        "IBAN": "ES6621000418401234567891",
        "photoURL": "https://okelan.s3.amazonaws.com/logo.png",
        "company": "Cárnicas Paco",
        "deliveryMode": "Prueba",
        "paymentMode": "Prueba",
        "active": true,
        "feeId": "59e78d76b8634f65967ec1b3",
        "phone": "620859192",
        "fee": "59e78d76b8634f65967ec1b3",
        "shippingAddresses": [
          {
            "province": "Asturias",
            "city": "Grado",
            "postalCode": 33820,
            "street": "Calle Asturias, 14 3°DCHA",
            "_id": "5a097de74c468c17af16d18e"
          },
          {
            "province": "adfasd",
            "city": "adsfasd",
            "postalCode": 23423,
            "street": "asdfasd",
            "_id": "5a097de74c468c17af16d18d"
          }
        ],
        "billingAddress": {
          "province": "adfasd",
          "city": "adsfasd",
          "postalCode": 23423,
          "street": "asdfasd"
        }
      }
    },
    {
      "_id": "5a0c0d91e5127378570f13b0",
      "price": 14,
      "productId": "5a09ac68e640a63e0520301f",
      "clientId": "59f350dfb8634f659680299e",
      "product": {
        "_id": "5a09ac68e640a63e0520301f",
        "erpId": "2355",
        "name": "Prueba",
        "description": "25",
        "weight": 25,
        "photoURL": "https://okelan.s3.amazonaws.com/camera.jpg",
        "familyId": "59e721e8b8634f65967eabf4"
      },
      "client": {
        "_id": "59f350dfb8634f659680299e",
        "contactName": "Pablo 2",
        "NIF": "12345678Z",
        "email": "pablogm.grao@gmail.com",
        "password": "$2a$10$gXn.G/q4ar3wbjSyBCu0XOVud0HL5l3d.2WXNab4cCfB3uToncQLm",
        "orders": [],
        "erpId": "14123",
        "IBAN": "ES6621000418401234567891",
        "photoURL": "https://okelan.s3.amazonaws.com/logo.png",
        "company": "Cárnicas Paco",
        "deliveryMode": "Prueba",
        "paymentMode": "Prueba",
        "active": true,
        "feeId": "59e78d76b8634f65967ec1b3",
        "phone": "620859192",
        "fee": "59e78d76b8634f65967ec1b3",
        "shippingAddresses": [
          {
            "province": "Asturias",
            "city": "Grado",
            "postalCode": 33820,
            "street": "Calle Asturias, 14 3°DCHA",
            "_id": "5a097de74c468c17af16d18e"
          },
          {
            "province": "adfasd",
            "city": "adsfasd",
            "postalCode": 23423,
            "street": "asdfasd",
            "_id": "5a097de74c468c17af16d18d"
          }
        ],
        "billingAddress": {
          "province": "adfasd",
          "city": "adsfasd",
          "postalCode": 23423,
          "street": "asdfasd"
        }
      }
    }
  ]
}
在这里您可以找到一些源文档:

是否可以使用聚合运算符在一个查询中完成此操作

感谢@dnickless提供最终解决方案:


]

好的,根据我对你的问题的理解,这里有一些应该与一些解释一起使用的东西:

Price.aggregate([{
    $match: { // keep this as your first stage as it will leverage an index on "clientId" (there should be one!) and limit the number of documents to process by the following stages nicely
        $or: [ // we want to consider all prices that
            { "clientId": new mongoose.Types.ObjectId(req.params.clientId) }, // are either for the chose client
            { "clientId": null } // or null - so valid for all clients
        ]
    }
}, {
    $sort: {
        "clientId": -1 // nulls will be at the end of our list so we can use $first later in the next stage in to pick the right price that should take precedence
    }
}, {
    $group: {
        _id: { "productId": "$productId" }, // look at all products individually
        "doc": { // take the first document per "productId" that hit the $group stage (the preceding $sort stage is needed for this to do the right thing)

            $first: "$$ROOT"
        }
    }
}, {
    $project: { // restore target structure
        "_id": "$doc._id", // you may or may not need this field
        "price": "$doc.price",
        "productId": "$_id.productId",
        "clientId": "$doc.clientId",
    }
} /* here you may want to include the $lookup stage if  you need the product information */ ])

您能发布一些示例文档吗?@dnickless我发布了一些所需文档的示例有些源文档是我们需要的,而不是您现在得到的结果。另外,一定是出了什么问题。您的最后一个$match阶段将只返回具有特定值的“clientId”的文档,因此不会返回示例中的第一个和最后一个文档@这就是:非常感谢!它按我的要求工作。我将在帖子中发布我的最终解决方案。
Price.aggregate([{
    $match: { // keep this as your first stage as it will leverage an index on "clientId" (there should be one!) and limit the number of documents to process by the following stages nicely
        $or: [ // we want to consider all prices that
            { "clientId": new mongoose.Types.ObjectId(req.params.clientId) }, // are either for the chose client
            { "clientId": null } // or null - so valid for all clients
        ]
    }
}, {
    $sort: {
        "clientId": -1 // nulls will be at the end of our list so we can use $first later in the next stage in to pick the right price that should take precedence
    }
}, {
    $group: {
        _id: { "productId": "$productId" }, // look at all products individually
        "doc": { // take the first document per "productId" that hit the $group stage (the preceding $sort stage is needed for this to do the right thing)

            $first: "$$ROOT"
        }
    }
}, {
    $project: { // restore target structure
        "_id": "$doc._id", // you may or may not need this field
        "price": "$doc.price",
        "productId": "$_id.productId",
        "clientId": "$doc.clientId",
    }
} /* here you may want to include the $lookup stage if  you need the product information */ ])