MongoDB查询数组和结果中包含的值仅包含这些值

时间:2015-11-19 03:29:50

标签: mongodb mongodb-query aggregation-framework

假设我有以下数据库:

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']}}]

问题在于这需要我知道要排除的所有可能的配料。有没有更好的方法来构建此查询?

1 个答案:

答案 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首先进行过滤仍然是明智的,因为它至少缩小到可能的结果,并且不需要整个集合的强力匹配。您使用它而不是问题中的“原始数组”,因为您演示的表单会将完全数组与精确数组匹配,并按顺序匹配。