我尝试根据子字段的子文档字段对字段集合进行排序。
这是我的文档的简化版本:
{
"_id": ObjectId("536900cdb4f805efff8b075b"),
"name": "el1",
"versions": [{
"releases": [{
"rd": ISODate("2064-05-05T15:36:10.098Z")
}, {
"rd": ISODate("2014-05-01T16:00:00Z")
}]
}, {
"releases": [{
"rd": ISODate("2064-05-04T15:36:10.098Z")
}, {
"rd": ISODate("2014-05-01T14:00:00Z")
}]
}]
}, {
"_id": ObjectId("536900f2b4f805efff8b075c"),
"name": "el2",
"versions": [{
"releases": [{
"rd": ISODate("2064-05-05T15:36:10.098Z")
}, {
"rd": ISODate("2014-05-01T17:00:00Z")
}]
}]
}
正如您所看到的,每个文档可能都有名为version
的子文档,每个version
可能有多个名为release
的子文档。我想根据rd
字段对主要文档进行排序,同时从sort
计算中排除从现在开始大于一年的所有日期。我不在乎对主文档中的子文档进行排序。
即。 ISODate("2064-05-05T15:36:10.098Z")
应该被忽略,因为距离ISODate("2014-05-01T16:00:00Z")
是好的。通过"忽略"我的意思是:不要在排序计算中使用该值,而不是:从结果中删除该文档。
我尝试了多种方法,包括map-reduce
和aggregation framework
,但却失败了。
这应该是成功排序的输出:
{
"_id": ObjectId("536900f2b4f805efff8b075c"),
"name": "el2",
"versions": [{
"releases": [{
"rd": ISODate("2064-05-05T15:36:10.098Z")
}, {
"rd": ISODate("2014-05-01T17:00:00Z")
}]
}]
}, {
"_id": ObjectId("536900cdb4f805efff8b075b"),
"name": "el1",
"versions": [{
"releases": [{
"rd": ISODate("2064-05-05T15:36:10.098Z")
}, {
"rd": ISODate("2014-05-01T16:00:00Z")
}]
}, {
"releases": [{
"rd": ISODate("2064-05-04T15:36:10.098Z")
}, {
"rd": ISODate("2014-05-01T14:00:00Z")
}]
}]
}
答案 0 :(得分:1)
请在下面的测试用例中找到您的问题的两个解决方案。 第一个解决方案使用MongoDB聚合框架。 对于每个文档,排序键根据您的时间限制从rd值中投射出来。 嵌套排序键结构通过两次展开然后为最大排序键分组来减少。 排序文档后,最后一个“项目”阶段将删除排序键。 第二种解决方案在客户端进行排序。 为了提高效率,它会处理每个doc以确定排序键并将其合并。 排序文档后,它会删除每个文档中的排序键。 如果排序密钥的存在是可以容忍的,则可以消除排除密钥的删除。
MongoDB的一个主要优势是文档可以很好地映射到编程语言数据结构。 所以我建议在寻找数据库解决方案之前首先尝试使用Ruby作为解决方案。 请注意,在Ruby解决方案中,直接使用rd_sort_key方法并非易事, 建议您使用条件和嵌套数组进行的操作相当复杂, 即使没有尝试在MongoDB的聚合框架中这样做。
如果您在没有限制的情况下获取整个结果集,则客户端解决方案是可以的。 如果使用限制,服务器端解决方案可能会节省一些传输时间。 但与往常一样,你应该进行衡量和比较。
我希望这会有所帮助,而且这很有趣,也许很有启发性。
test.rb
require 'mongo'
require 'date'
require 'test/unit'
def iso_date_to_time(s)
DateTime.parse(s).to_time
end
class MyTest < Test::Unit::TestCase
def setup
@pipeline = [
{'$project' => {
'name' => '$name',
'versions' => '$versions',
'rd_sort_key' => {
'$map' => {
'input' => '$versions', 'as' => 'version', 'in' => {
'$map' => {
'input' => '$$version.releases', 'as' => 'release', 'in' => {
'$cond' => [
{'$lt' => ['$$release.rd', @year_from_now]},
'$$release.rd',
nil
]}}}}}}},
{'$unwind' => '$rd_sort_key'},
{'$unwind' => '$rd_sort_key'},
{'$group' => {
'_id' => '$_id',
'name' => {'$first' => '$name'},
'versions' => {'$first' => '$versions'},
'rd_sort_key' => {'$max' => '$rd_sort_key'}}},
{'$sort' => {'rd_sort_key' => -1}},
{'$project' => {
'_id' => '$_id',
'name' => '$name',
'versions' => '$versions'}}
]
@coll = Mongo::MongoClient.new['test']['events_h']
@docs = [
{"_id" => BSON::ObjectId("536900cdb4f805efff8b075b"),
"name" => "el1",
"versions" => [{"releases" => [{"rd" => iso_date_to_time("2064-05-05T15:36:10.098Z")},
{"rd" => iso_date_to_time("2014-05-01T16:00:00Z")}]},
{"releases" => [{"rd" => iso_date_to_time("2064-05-04T15:36:10.098Z")},
{"rd" => iso_date_to_time("2014-05-01T14:00:00Z")}]}]
},
{"_id" => BSON::ObjectId("536900f2b4f805efff8b075c"),
"name" => "el2",
"versions" => [{"releases" => [{"rd" => iso_date_to_time("2064-05-05T15:36:10.098Z")},
{"rd" => iso_date_to_time("2014-05-01T17:00:00Z")}]}]
}]
@expected_names = [@docs.last['name'], @docs.first['name']]
@coll.remove
@coll.insert(@docs)
@year_from_now = Time.now + 60*60*24*365
end
test "aggregation sort with map and conditional" do
result = @coll.aggregate(@pipeline)
assert_equal(@expected_names, result.collect{|doc| doc['name']})
end
def rd_sort_key(doc, future_time_limit)
sort_key = nil
doc['versions'].each do |version|
version['releases'].each do |release|
rd = release['rd']
sort_key = sort_key ? [sort_key, rd].max : rd if rd < future_time_limit
end
end
sort_key
end
test "client sort with conditional" do
result = @coll.find.to_a
result.each{|doc| doc['rd_sort_key'] = rd_sort_key(doc, @year_from_now)}
result = result.sort{|a, b| b['rd_sort_key'] ? b['rd_sort_key'] <=> a['rd_sort_key'] : -1}
result.each{|doc| doc.delete('rd_sort_key')}
assert_equal(@expected_names, result.collect{|doc| doc['name']})
end
end
$ ruby test.rb
Loaded suite test
Started
..
Finished in 0.008794 seconds.
2 tests, 2 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
227.43 tests/s, 227.43 assertions/s