我想查询文档中数组元素的一个/多个匹配字段(也可以包括子文档)。

例如:

我的收藏包括以下文件:

{
    "_id": 1,
    "index": 1,
    "elements":[
        {
            "name":"test",
            "date":"Mon Sep 01 01:00:00 EEST 2014" ,
            "tag":1
        },
        {
            "name": "test2",
            "date": "Mon Sep 01 01:00:00 EEST 2014",
            "tag": 2
        },
        {
            "name": "test",
            "date": "Mon Sep 01 02:00:00 EEST 2014",
            "tag": 3
        }
    ]
},
{
    "_id":2,
    "index":2,
    "elements":[
        {
            "name":"test",
            "date":"Mon Sep 01 01:00:00 EEST 2014" ,
            "tag":1
        },
        {
            "name": "test2",
            "date": "Mon Sep 01 01:00:00 EEST 2014",
            "tag":2
        },
        {
            "name":"test",
            "date":"Mon Sep 01 01:10:00 EEST 2014",
            "tag":3
        }
    ]
},
{
    "_id": 3,
    "index": 3,
    "elements": [
        {
            "name": "test",
            "date": "Mon Sep 01 01:00:00 EEST 2014",
            "tag":1
        },
        {
            "name": "test2",
            "date": "Mon Sep 01 01:00:00 EEST 2014",
            "tag":2
        },
        {
            "name": "test",
            "date": "Mon Sep 01 01:10:00 EEST 2014",
            "tag":3
        }
    ]
}




我希望查询结果返回如下文档:

{
    "_id":1,
    "index": 1,
    "elements":[
        {
            "name":"test",
            "date":"Mon Sep 01 02:00:00 EEST 2014" ,
            "tag":3
        }
    ]
}


为此,我写了一个查询

Date dCriteria = new SimpleDateFormat("dd/MM/yy HH:mm:ss").parse("01/09/2014 05:00:00");

Query find = new Query( Criteria.where("index").is(3) );  //To find matching documents
find.fields().elemMatch(
    "elements",
    Criteria.where("name").is("test").and("date").gte(dCriteria)));

mongotemplate.findOne( find, Document.class );


这意味着在MongoDB shell命令中为:

db.collection.find(
    { "index": 3 },
    { "elements": {
        "$elemMatch": {
            "name": "test",
            "date": {
                "$gte": { "$date":"2014-09-01T02:00:000Z" }
            }
        }
    }
 )


但它返回以下结果:

{
    "_id": 0,
    "index": 0,
    "elements":[
        {
            "name": "test",
            "date": "Mon Sep 01 01:00:00 EEST 2014",
            "tag":1
        }
    ]
}


可以省略_id和index fileds,但是由于匹配条件而返回数组的第一个macing元素,匹配“ name”:“ test”或“ date”大于等于dCriteria,但是我要匹配两个条件与此同时。

为此,我必须使用$ elemMatch运算符来查询一个数组元素同时匹配多个字段。但是我不知道如何在我的投影中使用它的语法。

最佳答案

MongoDB将日期存储为UTC,这通常也是通常存储日期的最佳实践,因为它允许在不算出原始起点的情况下往返于各个时区。

因此,可以肯定的是,这里的实际日期确实是UTC,但是您传入的是从其他时区构造的值。驱动程序实际上使用时期时间戳序列化BSON请求,并注明“日期” BSON类型。这意味着您的输入现在被时区差异“扭曲”了。在这种情况下,需要3个小时代表UTC,因此转换后的日期比您想象的早3个小时。

如果您具有某个特定时区的日期的源数据,那么您打算首先转换此数据,如果要在本地时间显示值,则既要作为输入也要在用户界面中进行转换。

因此,将其转换或构造为UTC:

    // org.joda.time as Date constructor is deprecated
    Date dCriteria = new DateTime(2014,9,1,1,10,DateTimeZone.UTC).toDate();

    Query find = new Query( Criteria.where("index").is(3) );  //To find matching documents
    find.fields().elemMatch(
            "elements",
            Criteria.where("name").is("test").and("date").gte(dCriteria)
    );

    System.out.println(find);


如果您不确定结果查询的结构,请打印出来以进行常规调试。您确实应该在可以在生产中抑制的日志记录级别执行此操作。

如上所示,这与您引用的shell查询相同,但是您的代码本身并不存在,因为它使用的是本地时间而不是UTC。

您也几乎总是真的想要在查询中而不是在投影中进行元素匹配。一旦将positional $移至语句的查询部分,即可使用$elemMatch运算符来投影获得的“第一”匹配项。这样可以确保当数组中的任何元素都不实际符合您的条件时,您不会得到一个空数组。

09-30 16:49
查看更多