我有一个集合,其中的文档包含一些字段以及一个对象数组,每个对象都包含一个额外的对象数组。类似于以下内容:
{
customer_name:<text>, <other attributes>,
customer_orders: [
{ order_1_details:<text>, <other attributes>,
order_lineitems: [
{ commit_date: <some ISODate>, receipt_date: <ISODate>, <other attributes>},
{ commit_date: <some ISODate>, receipt_date: <ISODate>, <other attributes>}, ...
]
},
{ order_2_details:<text>, <other attributes>,
order_lineitems: [
{ commit_date: <some ISODate>, receipt_date: <ISODate>, <other attributes>},
{ commit_date: <some ISODate>, receipt_date: <ISODate>, <other attributes>}, ...
]
}, ...
]
}, ...
该结构基本上是客户与订单之间的一对多关系以及订单与订单项之间的一对多关系。按照上面的示例,我要获取1997-07-01 =< orderdate < 1997-10-01
的订单,并且至少存在commitdate < receiptdate
的一个行项目。首先,我想到了以下查询:
db.<collection>.aggregate(
{ $project: {
_id:0,
orders: {$filter:{
input: "$customer_orders",
as: "o",
cond: {$and: [{$gte:["$$o.orderdate", ISODate("1997-07-01T00:00:00Z")]}, {$lt:["$$o.orderdate", ISODate("1997-10-01T00:00:00Z")]}]}}
}}
},
{ $unwind: "$orders" },
{ $project: {
orderkey: "$orders.orderkey",
lcd_lt_lrc: {$lt: ["$orders.order_lineitems.commitdate", "$orders.order_lineitems.receiptdate"]}
}},
{ $match: { lcd_lt_lrc: true}},
{ $sort: {"orderkey": 1} }
);
但是我很快意识到第二个$project
存在问题。我对问题出在哪里的第一个猜测是,$lt
仅会比较lineitems数组中的第一个lineitem对象,而不是对每个元素都这样做,然后如果存在匹配项,则返回true。我可以通过以下示例来确认这一点:
db.tests.insertMany([
{
data: [
{a: ISODate("1995-08-06T00:00:00Z"), b: ISODate("2000-08-06T00:00:00Z")},
{a: ISODate("2018-08-06T00:00:00Z"), b: ISODate("2015-08-06T00:00:00Z")}
]
},
{
data: [
{a: ISODate("2018-08-06T00:00:00Z"), b: ISODate("2015-08-06T00:00:00Z")},
{a: ISODate("1995-08-06T00:00:00Z"), b: ISODate("2000-08-06T00:00:00Z")}
]
},
{
data: [
{a: ISODate("1995-08-06T00:00:00Z"), b: ISODate("2000-08-06T00:00:00Z")},
{a: ISODate("2005-08-06T00:00:00Z"), b: ISODate("2015-08-06T00:00:00Z")}
]
},
{
data: [
{a: ISODate("2015-08-06T00:00:00Z"), b: ISODate("2000-08-06T00:00:00Z")},
{a: ISODate("2016-08-06T00:00:00Z"), b: ISODate("2015-08-06T00:00:00Z")}
]
}
])
db.tests.aggregate(
{$project: {
_id: 0,
a_lt_b:{$lt:["$data.a", "$data.b"]}
}}
)
{ "a_lt_b" : true }
{ "a_lt_b" : false }
{ "a_lt_b" : true }
{ "a_lt_b" : false }
这意味着$lt
不能求值数组内部的所有元素。如果是,则结果应基于所有元素是a < b
还是至少一个元素是a < b
。如果我们考虑先验的话,那么文件一是矛盾的,因为它应该被评估为假。如果我们考虑后者,则文档1和2应该都评估为true。换句话说,$lt
仅考虑数组的第一个元素。
我的最终解决方案(更像是一种变通方法)是放松order_lineitems
,使用$match
丢弃所有行项不满足commit_date < receiptdate
的元组,然后重新分组或丢弃这样的元组使用$filter
(没有展开的行项),然后使用$match
丢弃order_lineitems
字段具有空数组的订单。
为了更好地让您了解这两种解决方案,请考虑上面的示例。如果我要使用上述解决方案:
解决方案1:
db.tests.aggregate(
{$unwind: "$data"},
{$project: {
_id: 1,
"data.a": 1,
"data.b": 2,
a_lt_b:{$lt:["$data.a", "$data.b"]}
}},
{$match: {a_lt_b: true}},
{$group: {
_id: "$_id"
}}
)
解决方案2:
db.tests.aggregate(
{$project: {
_id: 1,
data:{$filter:{
input: "$data",
as: "d",
cond: {$lt:["$$d.a", "$$d.b"]}
}},
}},
{$match: {data: {$ne:[]}}}
)
有更好的方法吗?
注意:
-mongoDB版本:4.0.6
-不能更改文档的结构。
-我已经尝试使用$cmp
代替$lt
-我没有尝试使用find()
而不是aggregate()
,但希望坚持使用聚合框架。