假设我有以下数据库:
pizzas = [{
name: "pizza1",
toppings: ['mushrooms', 'pepperoni', 'sausage']
},
{
name: "pizza2",
toppings: ['mushrooms', 'pepperoni']
},
{
name: "pizza3",
toppings: ['mushrooms', 'onions']
},
{
name: "pizza4",
toppings: ['mushrooms']
}]
现在我想拿到有“蘑菇”,“意大利辣香肠”或“洋葱”以及其中任意组合的比萨饼。然后查询可以是:
pizzas.find({toppings: ['mushrooms', 'pepperoni', 'onions']})
这将返回我的数据库中的所有四个比萨饼。但这是问题所在。如果我想要任意组合仅这三种配料,即比萨饼不能包含不同的“香肠”顶部,那会怎么样?对于此查询,我只想要返回“pizza2”,“pizza3”和“pizza4”。我可以进行如下查询:
pizzas.find({$and: [{toppings: ['mushrooms', 'pepperoni', 'onions']}, {$not: {toppings: ['sausage']}}]
问题在于这需要我知道要排除的所有可能的配料。有没有更好的方法来构建此查询?
答案 0 :(得分:2)
您基本上需要在存储的数组和所需列表之间找到“Set Difference”,并查看是否存储了任何不是所需成分之一的项目。因此,如果返回的列表大于0,则它包含列表中的另一个成分。
如果您至少拥有MongoDB 2.6,则可以在$setDifference
语句中使用$redact
运算符:
db.pizzas.aggregate([
{ "$match": {
"toppings": { "$in": [ "mushrooms", "pepperoni", "onions" ] }
}},
{ "$redact": {
"$cond": {
"if": {
"$eq": [
{ "$size": {
"$setDifference": [
"$toppings",
[ "mushrooms", "pepperoni", "onions" ]
]
}},
0
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
如果你的MongoDB比那个旧,那么你可以使用$where
在JavaScript中实现相同的逻辑:
db.pizzas.find({
"toppings": { "$in": [ "mushrooms", "pepperoni", "onions" ] },
"$where": function() {
return this.toppings.filter(function(topping) {
return [ "mushrooms", "pepperoni", "onions" ].indexOf(topping) == -1;
}).length == 0;
}
})
两者都通过相同的比较从结果中排除“pizza1”,.aggregate()
中的原生运算符更快:
{
"_id" : ObjectId("564d44a59f28c6e0feabceea"),
"name" : "pizza2",
"toppings" : [
"mushrooms",
"pepperoni"
]
}
{
"_id" : ObjectId("564d44a59f28c6e0feabceeb"),
"name" : "pizza3",
"toppings" : [
"mushrooms",
"onions"
]
}
{
"_id" : ObjectId("564d44a59f28c6e0feabceec"),
"name" : "pizza4",
"toppings" : [
"mushrooms"
]
}
请注意,使用$in
首先进行过滤仍然是明智的,因为它至少缩小到可能的结果,并且不需要整个集合的强力匹配。您使用它而不是问题中的“原始数组”,因为您演示的表单会将完全数组与精确数组匹配,并按顺序匹配。