查询以匹配数组的第n个文档

Query to Match on nth Document of an Array

本文关键字:文档 数组 查询      更新时间:2024-01-29

我是MongoDB的新手,我正在做一些练习。特别是我在这个练习中遇到了困难,我在这里报告了这个问题:

文件"Restaurant"的结构如下:

{
        "_id" : ObjectId("5704adbc2eb7ebe23f582818"),
        "address" : {
                "building" : "1007",
                "coord" : [
                        -73.856077,
                        40.848447
                ],
                "street" : "Morris Park Ave",
                "zipcode" : "10462"
        },
        "borough" : "Bronx",
        "cuisine" : "Bakery",
        "grades" : [
                {
                        "date" : ISODate("2014-03-03T00:00:00Z"),
                        "grade" : "A",
                        "score" : 2
                },
                {
                        "date" : ISODate("2013-09-11T00:00:00Z"),
                        "grade" : "A",
                        "score" : 6
                },
                {
                        "date" : ISODate("2013-01-24T00:00:00Z"),
                        "grade" : "A",
                        "score" : 10
                },
                {
                        "date" : ISODate("2011-11-23T00:00:00Z"),
                        "grade" : "A",
                        "score" : 9
                },
                {
                        "date" : ISODate("2011-03-10T00:00:00Z"),
                        "grade" : "B",
                        "score" : 14
                }
        ],
        "name" : "Morris Park Bake Shop",
        "restaurant_id" : "30075445"
}

编写一个MongoDB查询,为那些等级数组的第二个元素包含等级"a"并且在ISODate"2014-08-11T00:00Z"上得分为9的餐厅查找餐厅Id、名称和等级。

我写了这个查询:

db.restaurants.find(
{
    'grades.1': {
        'score': 'A',
        'grade': 9,
        'date' : ISODate("2014-08-11T00:00:00Z")
    }
},
{
    restaurant_id: 1,
    name: 1,
    grades: 1
});

这不起作用。提供的解决方案如下:

db.restaurants.find( 
    { "grades.1.date": ISODate("2014-08-11T00:00:00Z"), 
      "grades.1.grade":"A" , 
      "grades.1.score" : 9
    }, 
    {"restaurant_id" : 1,"name":1,"grades":1}
);

我的问题是:

  1. 有没有一种方法可以避免重复grades.1部分来编写查询
  2. 既然grades.1是文档对象,为什么我的查询是错误的

如果它能帮助回答我的问题,我正在使用MongoDB shell version: 3.2.4

编辑

多亏了这个问题,我找到了问题2的答案。

特别是,我发现顺序很重要。事实上,如果我执行以下查询,我会得到一个有效的结果:

db.restaurants.find({'grades.1': {'date': ISODate("2014-08-11T00:00:00Z"), 'grade':'A', score:9}}, {restaurant_id:1, name:1, grades:1})

请注意,此查询之所以有效,是因为所有子文档的"字段"都已指定,并且它们的指定顺序相同。

不是。但也许可以解释一下你"能"做什么:

  db.junk.find({
    "grades": { 
      "$elemMatch": {
        "date" : ISODate("2014-03-03T00:00:00Z"),
        "grade" : "A",
        "score" : 2
      }
    },
    "$where": function() {
      var grade = this.grades[0];
      return (
        grade.date.valueOf() == ISODate("2014-03-03T00:00:00Z").valueOf() &&
        grade.grade === "A" &&
        grade.score ==== 2
      )
    }
  })

$elemMatch允许您稍微缩短,但它不是数组的"第n"个元素。为了进一步缩小范围,您需要使用$where子句来检查"第n个"数组元素,以查看所有值是否匹配。

  db.junk.aggregate([
    { "$match": {
      "grades": { 
        "$elemMatch": {
          "date" : ISODate("2014-03-03T00:00:00Z"),
          "grade" : "A",
          "score" : 2
        }
      }
    }},
    { "$redact": {
      "$cond": {
        "if": {
          "$let": {
            "vars": { "grade": { "$arrayElemAt": [ "$grades", 0 ] } },
            "in": {
              "$and": [
                { "$eq": [ "$grade.date", ISODate("2014-03-03T00:00:00Z") ] },
                { "$eq": [ "$grade.grade", "A" ] },
                { "$eq": [ "$grade.score", 2 ] }
              ]
            }
          }
        },
        "then": "$$KEEP",
        "else": "$$PRUNE"
      }
    }}
  ])

您也可以使用.aggregate()$redact执行相同的逻辑。它运行得有点快,但基本事实现在应该很清楚了。

因此,使用"点表示法"为数组中的每个元素指定"第n个"位置,就像您已经做过的那样最有效和"简短"的编写方法。你不能把它做得更短或更好。

您的另一个尝试是在"grades.1"中查找与您提供的文档条件完全匹配的"文档"。如果由于任何原因,这些字段不存在,或者它们在存储的文档中确实处于"不同的顺序",则这样的查询条件将不匹配。