找到&按给定优先级排序两个订单

时间:2015-01-23 02:27:25

标签: mongodb mongodb-query

我有一个数组,我希望按照特定顺序排序,如

  1. 过滤地区
  2. 首先找到“权重”$gte,如果它没有返回结果,
  3. 找到“权重”$lt以返回结果
  4. 根据sorted顺序中的weight,数组始终为ascending。 这就是数组的样子:

       "shipping_charges" : [
            {
                    "region" : "region3",
                    "weight" : 100,
                    "rate" : 50
            },
            {
                    "region" : "region4",
                    "weight" : 100,
                    "rate" : 150
            },
            {
                    "region" : "region1",
                    "weight" : 200,
                    "rate" : 20
            },
            {
                    "region" : "region1",
                    "weight" : 500,
                    "rate" : 30
            },
            {
                    "region" : "region1",
                    "weight" : 1000,
                    "rate" : 40
            },
    ]
    

    到目前为止,我使用以下查询

    db.clients.find( { storeID: "asdfasdf" , 
                      "shipping_charges" : { 
                          $elemMatch : { "region" : "region1" ,
                                         "weight" : { $gte : 199 }
                                        } 
                         } 
                     }, { "shipping_charges.$" : 1 } ).pretty();
    

    这将给我第一次出现重量为$gte 199的记录。但是如果我用$gte 1050尝试相同的查询,我就什么也得不到,因为没有大于weight > 1050的平板。由于重量和区域是动态查询的,我将无法控制,但我想要的只是给定重量和区域的最佳匹配最高板块。

    我打算做什么? :我的想法是一个接一个地运行两个查询,首先查找给定重量的任何$gte,如果没有记录,请转到$lt(但它也会返回给我至少一个,因为我的数组按重量升序排序,所以我可能需要重新排序它们才能再次运行$lt查询。

    我想从这个群体中得到什么? :

     Query1 : 
     region : "region1", weight : 500,
    
     I want the result as 
            {
                    "region" : "region1",
                    "weight" : 500,
                    "rate" : 30
            },
    
    Query2 :
    region : "region1", weight : 1050, 
    
    I want the result as 
            {
                    "region" : "region1",
                    "weight" : 1000,
                    "rate" : 40
            },
    Query3:
    region : "region1", weight : 50
    I want the result as 
            {
                    "region" : "region1",
                    "weight" : 200,
                    "rate" : 20
            },    
    

    我想重写查询,以便它首先查找高于给定权重的任何内容,如果找不到,将找到所有应该是最接近的平板的最高位置。我知道这可以通过两个单独的查询一个接一个地运行,具体取决于计数但是可以合并并将其作为单个查询运行吗?

    理想的解决方案? : 有没有办法可以在一个查询中提供两个$sort订单,以便它首先查找$gte,然后使用$lt根据订单中的哪一个给出结果?如果没有,我如何使用“find”(首选)或使用聚合方法解决此问题?我更喜欢在mongoDB查询本身而不是使用完整数组,循环等的php/js解决方案中执行此操作,但是,哪个更好?我愿意接受建议。

    帮我解决这个问题

    1. 过滤集合{shipping_charges.region:$ region},
    2. find shipping_charges.weight> = lookup_weight& {shipping_charges.region:$ region} ,,
    3. 仅当没有上述记录时,按shipping_charges.weights降序排序
    4. find shipping_charges.weight< lookup_weight& {shipping_charges.region:$ region},获取第一个
    5. 这应该是我猜的。

3 个答案:

答案 0 :(得分:0)

更新:这与lookup_weight匹配得最近。

我认为就我理解OP而言,他要求的是与lookup_weight最近的数字(向上或向下)。 (又名是最小的腹肌差异)。 Mongodb有$near运算符,但仅适用于地理空间数据。

如果您无法更改数据字段 - 那么此方法可以正常工作(没有abs,因此我们必须使用$cond来反转我们的$subtract,以便我们可以比较值小于那些大于$sort的值。

col.aggregate([{"$project": 
{ "difference": 
{"$cond": [ { "$gte": ["$weight", lookup_weight] }, 
{ "$subtract": ["$weight", lookup_weight] }, 
{"$subtract": [lookup_weight, "$weight"] } ] }, 
"weight": "$weight", "rate": "$rate", "region": "$region"} }, 
{"$sort": {"difference": 1}}, 
{"$limit": 1}  ])

答案 1 :(得分:0)

我不认为你的模特是最好的,但如果没有更清楚地解释你的全部意图,那么提出其他建议并不容易。我会解释一下我认为的好方法。

我可以告诉你一种通过聚合来暴力破解的方法,但这并不是非常简单或理想。更好的想法是稍微改变你的模式,如:

        {
                "region" : "region1",
                "minWeight" : 500,
                "maxWeight": 999
                "rate" : 30
        },
        {
                "region" : "region1",
                "minWeight" : 1000,
                "maxWeight": 999999
                "rate" : 40
        }

现在有" minWeight"和" maxWeight"查询范围很容易:

db.clients.find(
   { 
      "storeID": "asdfasdf",
      "shipping_charges" : { 
         "$elemMatch": { 
           "region" : "region1" , 
           "minWeight": { "$lte": 1050 },
           "maxWeight": { "$gte": 1050 } 
         }
      }
    }, 
    { "shipping_charges.$" : 1 }
  ).pretty();

这很简单,很简单就是你真正想要的,因为它很快。只要确保你使用合理的" maxWeight"在您的最后一个条目中,在每个保存中一致并过滤您的查询输入,以便如果在该数字上输入任何内容,那么该组最大值将用于查询


证明文件

包含范围和排序顺序的完整更正数据:

{

  "storeID": "asdfasdf",
  "shipping_charges" : [
    {
            "region" : "region1",
            "minWeight": 0,
            "maxWeight" : 200,
            "rate" : 20
    },
    {
            "region" : "region1",
            "minWeight" : 201,
            "maxWeight" : 999,
            "rate" : 30
    },
    {
            "region" : "region1",
            "minWeight" : 1000,
            "maxWeight" : 999999,
            "rate" : 40
    },
    {
            "region" : "region3",
            "minWeight": 0,
            "maxWeight" : 999999,
            "rate" : 50
    },
    {
            "region" : "region4",
            "minWeight": 0,
            "maxweight" : 999999,
            "rate" : 150
    }
  ]
}

确保最佳指数:

db.store.ensureIndex({ "storeID": 1, "shipping_charges.region": 1 })

这里不能使用权重,因为索引中不止一个复杂字段,这是不允许的。理想情况下,此数据不在数组中,而是在查询中所需的所有字段上的最佳索引的单独文档。

每个测试用例:

<强> Q1:

db.store.find(
   { 
      "storeID": "asdfasdf",
      "shipping_charges" : { 
         "$elemMatch": { 
           "region" : "region1" , 
           "minWeight": { "$lte": 500 },
           "maxWeight": { "$gte": 500 } 
         }
      }
    }, 
    { "shipping_charges.$" : 1 }
  ).pretty();

结果:

{
    "_id" : ObjectId("54c20f6137ad0dcb15cb8787"),
    "shipping_charges" : [
            {
                    "region" : "region1",
                    "minWeight" : 201,
                    "maxWeight" : 999,
                    "rate" : 30
            }
    ]
}

<强> Q2

db.store.find(
   { 
      "storeID": "asdfasdf",
      "shipping_charges" : { 
         "$elemMatch": { 
           "region" : "region1" , 
           "minWeight": { "$lte": 1050 },
           "maxWeight": { "$gte": 1050 } 
         }
      }
    }, 
    { "shipping_charges.$" : 1 }
  ).pretty();

结果:

{
    "_id" : ObjectId("54c20f6137ad0dcb15cb8787"),
    "shipping_charges" : [
            {
                    "region" : "region1",
                    "minWeight" : 1000,
                    "maxWeight" : 999999,
                    "rate" : 40
            }
    ]
}

<强> Q3

db.store.find(
   { 
      "storeID": "asdfasdf",
      "shipping_charges" : { 
         "$elemMatch": { 
           "region" : "region1" , 
           "minWeight": { "$lte": 50 },
           "maxWeight": { "$gte": 50 } 
         }
      }
    }, 
    { "shipping_charges.$" : 1 }
  ).pretty();

结果:

{
    "_id" : ObjectId("54c210f737ad0dcb15cb8788"),
    "shipping_charges" : [
            {
                    "region" : "region1",
                    "minWeight" : 0,
                    "maxWeight" : 200,
                    "rate" : 20
            }
    ]
}

仅匹配符合条件且无开销的文档。

答案 2 :(得分:0)

您无法在单个find()语句中实现此功能,但您可以按以下方式对其进行汇总。将shipping_charges存储为已排序的数组更有意义。

让:

var weight = 50;
var region = "region1";
  • Match所有至少有一个shipping_charges的文档 指定region
  • 的元素
  • Unwind shipping_charges字段。
  • Match那些具有相同region的未卷绕文档 我们正在寻找。
  • Group一起_id,以获取最后Shipping_charges 元素,也是最重的。这个元素我们会 使用,以防我们的搜索权重大于所有 shipping_weight元素的个人权重。
  • 再次
  • Grouplargest元素转换为array元素 单个元素。我们这样做是为了方便unwind的使用 舞台后期即将开始。
  • Project字段res作为布尔值数组,表明我们是否 找到了匹配的shipping_charges元素。如果所有的元素 在这个数组是假的,那么我们没有找到匹配,我们需要 将largest元素显示为匹配。
  • Unwind shipping_charges元素。
  • Match具有相同区域且具有适当性的那些 weight
  • Group这些元素一起获取第一个匹配元素 每个文档或每个文档的largest元素。

汇总代码:

db.collection.aggregate([
{$match:{"shipping_charges.region":region}},
{$unwind:"$shipping_charges"},
{$match:{"shipping_charges.region":region}},
{$group:{"_id":"$_id",
         "largest":{$last:"$shipping_charges"},
         "shipping_charges":{$push:"$shipping_charges"}}},
{$group:{"_id":"$_id",
         "largest":{$push:"$largest"},
         "shipping_charges":{$first:"$shipping_charges"}}},
{$project:{"largest":1,
           "shipping_charges":1,
           "res":{$map:{
                   "input":"$shipping_charges",
                   "as":"x",
                   "in":{$cond:[{$gte:["$$x.weight",weight]},
                                true,false]}}}}},
{$project:{"largest":1,
           "shipping_charges":1,
           "r":{$anyElementTrue:["$res"]}}},
{$project:{"shipping_charges":{$cond:[{$eq:["$r",true]},
          "$shipping_charges","$largest"]},"r":1}},
{$unwind:"$shipping_charges"},
{$match:{$or:[{"shipping_charges.weight":{$gte:weight}},
              {"r":false}]}},
{$group:{"_id":"$_id",
         "shipping_charges":{$first:"$shipping_charges"}}}
])

虽然聚合涉及许多步骤,但初始match条件可以作为减少文档流入的良好过滤机制。