MongoDB查询DBRefs数组的子集

时间:2016-03-17 21:23:37

标签: mongodb mongodb-query aggregation-framework

我的主要对象如下:

{
"_id" : ObjectId("56eb0a06560fd7047318465a"),
 ...
"intervalAbsenceDates" : [
    DBRef("ScheduleIntervalContainer", ObjectId("56eb0a06560fd7047318463b")),
    DBRef("ScheduleIntervalContainer", ObjectId("56eb0a05560fd70473184467")),
    DBRef("ScheduleIntervalContainer", ObjectId("56eb0a05560fd70473184468")),
    DBRef("ScheduleIntervalContainer", ObjectId("56eb0a05560fd70473184469")),

嵌入式ScheduleIntervalContainer对象如下所示:

{
"_id" : ObjectId("56eb0a06560fd7047318463b"),
"end" : ISODate("2022-08-23T07:06:00Z"),
"available" : true,
"confirmation" : true,
"start" : ISODate("2022-08-19T09:33:00Z")
}

现在我将查询所有ScheduleIntervalContainers,其中start和end在一个范围内。 我已经尝试了很多,但我甚至不能通过id查询一个ScheduleIntervalContainer。 这是我的方法:

db.InstitutionUserConnection.find( { 
"intervalAbsenceDates" : { 
    "$ref" : "ScheduleIntervalContainer", 
    "$id" : ObjectId("56eb0a05560fd7047318446d")
}
})

任何人都可以给我一个提示,告诉我如何查询在时间范围内有开始和结束的所有ScheduleIntervalContainers。

1 个答案:

答案 0 :(得分:3)

使用DBRef充满了问题,使用方式有些过时"过时"因为它更像是一个"鞋拔"解决方案请求提供一种机制来提供对外部集合数据的引用,而不是一个经过深思熟虑的解决方案。

一般建议不要使用DBRef而是简单地使用普通ObjectId或其他唯一标识符来解析实际的"集合"甚至"数据库容器"与其他信息一起,是存储在文档中的另一个标准属性,或者只是代码中用于标识目标集合的简单外部引用。

基本BSON问题

这样做的一个很好的理由是,人们经常会犯一些常见错误(就像你一样)解释"序列化"从包含DBRef的存储对象输出实际上是"属性"出现在对象上。事实并非如此,因为实际上对象具有自己的BSON类型,就像DateObjectId一样。序列化表单有效的唯一时间是与"严格模式一起使用" JSON解析器,它会生成实际的DBRef对象。

manual itself对此事实并不特别有用,并说" DBRefs有以下字段" 。这是误导性的,因为这些属性实际上可用作查询字段,除了$wheremapReduce的JavaScript处理可用的对象检查之外,不会公开。并不是一个很好的方法来使用其中任何一个"查询"目的。

因此,这些属性不可用于查询,但您当然可以直接从API中指定BSON表单。如:

db.InstitutionUserConnection.find({ 
    "intervalAbsenceDates": DBRef(
        "ScheduleIntervalContainer",  
        ObjectId("56eb0a05560fd70473184469")
    ) 
})

由于在查询中发送了正确的BSON表单并且元素实际匹配,因此可以正确解析和匹配。

根据这一原则,我们可以继续讨论"范围"正如问题所述。

MongoDB不"真的" 做联接

直到最近发布的版本(MongoDB 3.2.x系列),该声明实际上是" MongoDB 做加入" ,这是一般的设计哲学与关系数据库的区别。

一般的口头禅"" "加入费用昂贵" 因此在分布式数据系统中无法很好地扩展比如MongoDB主要用于什么。

因此,如果您要求引用DBRef引用的文档中的属性,那么您基本上没有运气。在这种情况下,基于外部属性过滤结果的唯一可能操作是:

  1. 查看主集合中的所有数据,加载到整体查询或单独处理

  2. 对于每个检索到的文档,查找并将DBRef值展开为其目标集合数据。

  3. 过滤掉外部参考文献中扩展数据实际上不符合条件的文件。

  4. 这意味着所有"扩张"和"过滤" 必须在"客户端"到数据库而不是服务器本身。根本没有机制可以做到这一点,因此您最终会通过网络连接提取大量数据。

    如果使用DBRef,即使使用现代版本,这在服务器上也无法执行。同样,它是相同的BSON类型问题,因为"来源"包含DBRef种类型和"目标"包含ObjectId种类型。

    但是,如果你可以简单地接受你的"范围"正在关注"创建日期"在任何 ObjectId中固有存在的数据,当然有另一种方法不涉及"加入"。

    过滤ObjectId"范围"

    每个ObjectId以4字节开头,表示创建ObjectId时的当前时间戳值(不包括毫秒)。这通常是"插入时间的良好指标。对于有问题的文件使用它。

    通过此,您可以确定"创建日期"引用DBRef的文档大约等于该文档在目标集合中使用的ObjectId值部分。这允许你基本上构建一个"范围" ObjectId值的值将落在给定范围之间。因此,您可以构建可用于范围运算符的DBRef BSON对象:

    // Define start and end dates to query
    var dateStart = new Date("2016-03-17T19:48:21Z"), // equal to 56eb0a05
        dateEnd = new Date("2016-03-17T19:48:25Z");   // equal to 56eb0a09
    
    // Convert to hex and pad to ObjectId length
    var startRange = new ObjectId(
        ( dateStart.valueOf() / 1000 ).toString(16) + "0000000000000000"
    ),
    // Yields ObjectId("56eb0a050000000000000000")
    endRange = new ObjectId(
        ( dateEnd.valueOf() / 1000 ).toString(16) + "ffffffffffffffff"
    );
    // Yields ObjectId("56eb0a09ffffffffffffffff")
    
    // Now query with contructed DBRef values
    
    db.InstitutionUserConnection.find({ 
        "intervalAbsenceDates": {
            "$elemMatch": {
                "$gte": DBRef("ScheduleIntervalContainer",startRange),
                "$lt": DBRef("ScheduleIntervalContainer",endRange),
            }
        }
    })
    

    只要"创建"正是您正在寻找的,那么该方法应该足以选择匹配的父项,而无需先扩展数组中的DBRef值以进行进一步的检查。

    反向案例查询

    当然,这里的另一种情况是简单地查询"加入"首先收集,然后在" master"中查找文档。包含ObjectIdDBRef值的集合。这当然意味着要发布多个查询,但它确实可以解决扩展每个DBRef只是为了匹配相关属性的问题:

    // Create array of matching DBRef values
    var refs = db.ScheduleIntervalContainer.find({
        "start" { "$lte": targetDate },
        "end": { "$gte": targetDate }
    }).map(function(doc) {
        return DBRef("ScheduleIntervalContainer",doc._id)
    });
    
    // Find documents that match the DBRef's within the array
    db.InstitutionUserConnection.find({ 
        "intervalAbsenceDates": { "$in": refs } 
    })
    

    此实用性因相关集合的匹配数而异,导致数组传递给$in,但实际上会产生所需的结果。

    实际上在做Joins

    我之前提到过"现代" MongoDB版本现在有了一种方法来加入"来自不同馆藏的数据。这是$lookup聚合管道运算符。

    但是这可以用来加入"数据,DBRef的当前用法在这里不起作用。我之前也提到过,基本问题是数组中的数据是DBRef,但引用的集合中的数据是ObjectId

    因此,如果您想使用$lookup方法,则首先需要使用纯ObjectId值代替现有的DBRef值:

    {
      "_id" : ObjectId("56eb0a06560fd7047318465a"),
      "intervalAbsenceDates" : [
        ObjectId("56eb0a06560fd7047318463b"),
        ObjectId("56eb0a05560fd70473184467"),
        ObjectId("56eb0a05560fd70473184468"),
        ObjectId("56eb0a05560fd70473184469")
      ]
    }
    

    使用该结构中的数据,您可以使用$lookup和其他聚合管道方法来返回实际匹配相关属性值的文档。 I.e "end"在相关对象中:

     db.InstitutionUserConnection.aggregate([
         // Presently you need to unwind the array first
         { "$unwind": "$intervalAbsenceDates" },
    
         // Then $lookup to get a resulting array of matches for each member
         { "$lookup": {
             "from": "ScheduleIntervalContainer",
             "localField": "intervalAbsenceDates",
             "foreignField": "_id",
             "as": "absenceDates"
         }},
    
         // unwind the array result field as well
         { "$unwind": "$absenceDates" },
    
         // Now reform the documents
         { "$group": { 
             "_id": "$_id",
             "intervalAbsenceDates": { "$push": "$absenceDates" }
         }},
    
         // Then query on the "end" property for the range
         { "$match": { 
             "intervalAbsenceDates": {
                 "$elemMatch": {
                     "end": {
                         "$gte": new Date("2016-03-23"),
                         "$lt": new Date("2016-03-24")
                     }
                 }
             }
         }}
     ])
    

    $lookup的当前行为是您不能直接处理文档中的数组属性,因此"$lookup on ObjectId's in an array"中显示的过程用于将当前数组替换为来自另一个的扩展对象集合。

    一旦这里的操作实际上产生了一个现在嵌入了相关数据的文档,它就是一个简单的过程,可以查看数组中文档的属性,看它们是否与查询条件匹配。

    结论

    这一切都应该表明DBRef不是存储引用的好主意。 Whist可以使用ObjectId值来解决问题,您通常希望使用普通ObjectId或其他键值作为参考,并通过其他方式解决它们。即使解决方法足够,它也可以使用普通ObjectId值或其他任何具有自然范围的值。

    使用"值"在这样的" join"中引用的属性,当然无论使用DBRef还是其他值,如果没有$lookup在服务器上的查询条件中使用它,那么这是不可能的。首先需要将所有数据加载到客户端,然后使用对数据库的其他查询进行解析,然后才能检查这些属性以进行过滤。

    由于$lookup的机制实际上会产生一种形式,如果你"嵌入"首先是数据,然后是#34;嵌入"通常是正确的方法,因为数据已经存在于源集合中并可供查询。

    有很多"恐吓媒体"关于16MB的BSON限制,并说这就是为什么你将数据保存在另一个集合中。有时这确实适用,但大部分时间都没有。毕竟,16MB实际上是非常大量的数据,并且比一般应用程序实际使用的数据还要多。

    引自 MongoDB:权威指南

      

    为了让你知道16MB的数量,战争与和平的全文只有3.14MB。

    检查需要访问"嵌入表单的查询"无论如何,有争议的是,如果你可以存储一个DBRefObjectId的数组或任何嵌入数据,那么存储他们实际指向的所有内容实际上并不是那么多拉伸。

    一般的教训是,您应该根据应用程序应用于数据的实际使用模式进行设计。如果您正在查询相关数据"在所有的时间里,将数据全部保存在一个集合中是最有意义的。当然,其他因素也适用,但请始终牢记在性能方面的考虑因素。