如何在MongoDB集合中查找给定标准的文档和单个子文档

时间:2014-08-30 21:27:39

标签: mongodb mongodb-query aggregation-framework

我有产品系列。每个产品都包含一系列项目。

> db.products.find().pretty()
{
    "_id" : ObjectId("54023e8bcef998273f36041d"),
    "shop" : "shop1",
    "name" : "product1",
    "items" : [
            {
                    "date" : "01.02.2100",
                    "purchasePrice" : 1,
                    "sellingPrice" : 10,
                    "count" : 15
            },
            {
                    "date" : "31.08.2014",
                    "purchasePrice" : 10,
                    "sellingPrice" : 1,
                    "count" : 5
            }
    ]
}

那么,请你给我一个建议,我怎样才能查询MongoDB以检索所有只有单个项目的产品,哪个日期等于我传递给查询作为参数的日期。

“31.08.2014”的结果必须是:

    {
    "_id" : ObjectId("54023e8bcef998273f36041d"),
    "shop" : "shop1",
    "name" : "product1",
    "items" : [
            {
                    "date" : "31.08.2014",
                    "purchasePrice" : 10,
                    "sellingPrice" : 1,
                    "count" : 5
            }
    ]
}

3 个答案:

答案 0 :(得分:32)

您正在寻找的是the positional $运算符和"投影"。对于单个字段,您需要使用"点符号"匹配所需的数组元素,以便使用多个字段$elemMatch

db.products.find(
    { "items.date": "31.08.2014" },
    { "shop": 1, "name":1, "items.$": 1 }
)

或多个匹配字段的$elemMatch

db.products.find(
    { "items":  { 
        "$elemMatch": { "date": "31.08.2014",  "purchasePrice": 1 }
    }},
    { "shop": 1, "name":1, "items.$": 1 }
)

这些只适用于单个数组元素,只返回一个。如果您希望从条件中返回多个数组元素,则需要使用聚合框架进行更高级的处理。

db.products.aggregate([
    { "$match": { "items.date": "31.08.2014" } },
    { "$unwind": "$items" },
    { "$match": { "items.date": "31.08.2014" } },
    { "$group": {
        "_id": "$_id",
        "shop": { "$first": "$shop" },
        "name": { "$first": "$name" },
        "items": { "$push": "$items" }
    }}
])

或者可能是自MongoDB 2.6以来更短/更快的形式,其中您的项目数组包含唯一条目:

db.products.aggregate([
    { "$match": { "items.date": "31.08.2014" } },
    { "$project": {
        "shop": 1,
        "name": 1,
        "items": {
            "$setDifference": [
                { "$map": {
                    "input": "$items",
                    "as": "el",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$el.date", "31.08.2014" ] },
                            "$$el",
                            false 
                        ]
                    }
                }},
                [false]
            ]
        }
    }}
])

或者可能与$redact,但有点做作:

db.products.aggregate([
    { "$match": { "items.date": "31.08.2014" } },
    { "$redact": {
        "$cond": [
             { "$eq": [ { "$ifNull": [ "$date", "31.08.2014" ] }, "31.08.2014" ] },
             "$$DESCEND",
             "$$PRUNE"
         ]
    }}
])

更现代,您可以使用$filter

db.products.aggregate([
  { "$match": { "items.date": "31.08.2014" } },
  { "$addFields": {
    "items": {
      "input": "$items",
      "cond": { "$eq": [ "$$this.date", "31.08.2014" ] }
    }
  }}
])

在多个条件下,$elemMatch中的$and$filter

db.products.aggregate([
  { "$match": { 
    "$elemMatch": { "date": "31.08.2014",  "purchasePrice": 1 }
  }},
  { "$addFields": {
    "items": {
      "input": "$items",
      "cond": { 
        "$and": [
          { "$eq": [ "$$this.date", "31.08.2014" ] },
          { "$eq": [ "$$this.purchasePrice", 1 ] }
        ]
      }
    }
  }}
])

所以它只取决于你是否总是希望单个元素匹配或多个元素,然后哪种方法更好。但是在可能的情况下,.find()方法通常会更快,因为它缺少其他操作的开销,而最后形式的操作根本不会远远落后于其他操作。

作为旁注,您的"日期"表示为字符串,这不是一个很好的想法。请考虑将这些更改为正确的Date对象类型,这将在未来对您有很大帮助。

答案 1 :(得分:0)

Mongo支持子查询的点表示法。

请参阅:http://docs.mongodb.org/manual/reference/glossary/#term-dot-notation

根据您的驱动程序,您需要以下内容:

db.products.find({"items.date":"31.08.2014"});

请注意,即使您的驱动程序通常不需要,该属性也带有点表示法的引号。

答案 2 :(得分:0)

基于我使用此解决方案的 Neil Lunn's 代码,它自动包含所有第一级键(但如果需要,您也可以排除键):

db.products.find(
    { "items.date": "31.08.2014" },
    { "shop": 1, "name":1, "items.$": 1 }
    { items: { $elemMatch: { date: "31.08.2014" } } },
)

有多个要求:

db.products.find(
    { "items":  { 
        "$elemMatch": { "date": "31.08.2014",  "purchasePrice": 1 }
    }},
    { items: { $elemMatch: { "date": "31.08.2014",  "purchasePrice": 1 } } },
)