我的主要对象如下:
{
"_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。
答案 0 :(得分:3)
使用DBRef
充满了问题,使用方式有些过时"过时"因为它更像是一个"鞋拔"解决方案请求提供一种机制来提供对外部集合数据的引用,而不是一个经过深思熟虑的解决方案。
一般建议不要使用DBRef
而是简单地使用普通ObjectId
或其他唯一标识符来解析实际的"集合"甚至"数据库容器"与其他信息一起,是存储在文档中的另一个标准属性,或者只是代码中用于标识目标集合的简单外部引用。
这样做的一个很好的理由是,人们经常会犯一些常见错误(就像你一样)解释"序列化"从包含DBRef
的存储对象输出实际上是"属性"出现在对象上。事实并非如此,因为实际上对象具有自己的BSON类型,就像Date
和ObjectId
一样。序列化表单有效的唯一时间是与"严格模式一起使用" JSON解析器,它会生成实际的DBRef
对象。
manual itself对此事实并不特别有用,并说" DBRefs有以下字段" 。这是误导性的,因为这些属性不实际上可用作查询字段,除了$where
或mapReduce
的JavaScript处理可用的对象检查之外,不会公开。并不是一个很好的方法来使用其中任何一个"查询"目的。
因此,这些属性不可用于查询,但您当然可以直接从API中指定BSON表单。如:
db.InstitutionUserConnection.find({
"intervalAbsenceDates": DBRef(
"ScheduleIntervalContainer",
ObjectId("56eb0a05560fd70473184469")
)
})
由于在查询中发送了正确的BSON表单并且元素实际匹配,因此可以正确解析和匹配。
根据这一原则,我们可以继续讨论"范围"正如问题所述。
直到最近发布的版本(MongoDB 3.2.x系列),该声明实际上是" MongoDB 不做加入" ,这是一般的设计哲学与关系数据库的区别。
一般的口头禅"" "加入费用昂贵" 因此在分布式数据系统中无法很好地扩展比如MongoDB主要用于什么。
因此,如果您要求引用DBRef
引用的文档中的属性,那么您基本上没有运气。在这种情况下,基于外部属性过滤结果的唯一可能操作是:
查看主集合中的所有数据,加载到整体查询或单独处理
对于每个检索到的文档,查找并将DBRef
值展开为其目标集合数据。
过滤掉外部参考文献中扩展数据实际上不符合条件的文件。
这意味着所有"扩张"和"过滤" 必须在"客户端"到数据库而不是服务器本身。根本没有机制可以做到这一点,因此您最终会通过网络连接提取大量数据。
如果使用DBRef
,即使使用现代版本,这在服务器上也无法执行。同样,它是相同的BSON类型问题,因为"来源"包含DBRef
种类型和"目标"包含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"中查找文档。包含ObjectId
中DBRef
值的集合。这当然意味着要发布多个查询,但它确实可以解决扩展每个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
,但实际上会产生所需的结果。
我之前提到过"现代" 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。
检查需要访问"嵌入表单的查询"无论如何,有争议的是,如果你可以存储一个DBRef
或ObjectId
的数组或任何嵌入数据,那么存储他们实际指向的所有内容实际上并不是那么多拉伸。
一般的教训是,您应该根据应用程序应用于数据的实际使用模式进行设计。如果您正在查询相关数据"在所有的时间里,将数据全部保存在一个集合中是最有意义的。当然,其他因素也适用,但请始终牢记在性能方面的考虑因素。