【问题标题】:Aggregation query operator excluding values with repeated key MongoDB聚合查询运算符排除具有重复键 MongoDB 的值
【发布时间】:2018-04-27 19:08:06
【问题描述】:

我已经实现了一个系统,其中有两种价格:

  1. 特定客户的特定价格(clientId、productId)
  2. 许多客户的共同价格(feeId、productId)

我的架构是灵活的,即有具有 clientId (1) 的价格和具有 feeId (2) 的价格。但它们都必须有一个 productId。

只有一个 Price 具有相同的 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"
        }
      }
    }
  ]
}

这里可以找到一些源文件:https://pastebin.com/qBDqKa5y

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


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

  Price.aggregate([
{
  $lookup:
    {
      'from': 'products',
      'localField': 'productId',
      'foreignField': '_id',
      'as': 'product'
    }
},
{
  $unwind: '$product'
},
{
  $lookup:
    {
      'from': 'clients',
      'localField': 'clientId',
      'foreignField': '_id',
      'as': 'client'
    }
},
{
  $unwind: '$client'
},
{
  $match: {
    $and: [
      {
        "product.familyId": new mongoose.Types.ObjectId(req.params.familyId)
      },
      {
        $or: [
          {"clientId": new mongoose.Types.ObjectId(req.params.clientId)},
          {"feeId": new mongoose.Types.ObjectId(req.body.feeId)}
        ]
      }
    ]
  }
},
{
  $sort:
    {
      "clientId":
        -1
    }
},
{
  $group: {

    _id: {
      "productId":
        "$productId"
    }
    ,
    "doc":
      {
        $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",
    "feeId": "$doc.feeId",
    "promoPrice": "$doc.promoPrice"
  }
},
{
  $lookup:
    {
      'from': 'products',
      'localField': 'productId',
      'foreignField': '_id',
      'as': 'product'
    }
},
{
  $unwind: '$product'
},
{
  $lookup:
    {
      'from': 'clients',
      'localField': 'clientId',
      'foreignField': '_id',
      'as': 'client'
    }
},
{
  $unwind: '$client'
}

])

【问题讨论】:

  • 能否请您发布一些示例文件?
  • @dnickless 我已经发布了一些所需文件的示例
  • 一些源文件是我们需要的,而不是你现在得到的结果。
  • 另外,肯定有问题。您的最后一个 $match 阶段只会返回具有特定值的“clientId”的文档,而不是示例中的第一个和最后一个文档......?!
  • @dnickless 这里是:pastebin.com/qBDqKa5y

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


【解决方案1】:

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

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 */ ])

【讨论】:

  • 非常感谢!它按我的意愿工作。我将在帖子中发布我的最终解决方案。
猜你喜欢
  • 2015-11-19
  • 1970-01-01
  • 2021-03-09
  • 1970-01-01
  • 2014-05-12
  • 2019-04-12
  • 1970-01-01
  • 1970-01-01
  • 2019-08-09
相关资源
最近更新 更多