我正在尝试学习MongoDB,到目前为止它已经很棒了。但是我碰到了一个情况,我不太清楚如何解决它。希望有人可以帮助我,并提前感谢。
我想获取(整个)数组值在查询中的记录。例如:
记录1:
{"name" : "Mango Shake",
"ingredients" : [{"type" : "fruit", "name" : "mango"},
{"type" : "milk", "name" : "soy milk"}]}
记录2:
{"name" : "Mango Banana Shake",
"ingredients" : [{"type" : "fruit", "name" : "mango"},
{"type" : "milk", "name" : "soy milk"},
{"type" : "fruit", "name" : "banana"}]}
记录3:
{"name" : "Milk Shake",
"ingredients" : [{"type" : "milk", "name" : "soy milk"}]}
然后我会有一个类似
的查询{"ingredients" : {$all : [{"type" : "fruit", "name" : "mango"},
{"type" : "milk", "name" : "soy milk"},
{"type" : "fruit", "name" : "strawberry"}]}}
因为我有芒果,豆浆和草莓。所以我想知道我能做些什么。显然这不返回任何东西,因为查询不能有额外的东西。如果我使用$in
那么所有人都将返回,但我不能做芒果香蕉奶昔,因为我没有香蕉..
所以我唯一需要的是第一个和最后一个。任何的想法?欣赏它:)
答案 0 :(得分:0)
我想不出用find做这个的方法。但你可以使用mapReduce来完成。
您的地图功能将迭代当前检查文档的成分,并且只有当所有字段都是您拥有的成分时才会发出它。您不需要为此使用reduce函数,因此发出值的键应该是饮料文档的_id。
当键是唯一的时,永远不应该调用reduce函数(reduce函数用于确定为同一个键发出多个值时会发生什么)。我不认为你可以省略reduce函数,所以你应该编写一个只返回values数组的第一个元素的函数。
答案 1 :(得分:0)
如果您愿意,Map reduce可以使用,或者您只需使用$并为每种成分使用一个条件:
http://docs.mongodb.org/manual/reference/operator/and/
我已经使用过$和之前的类似查询需求。不幸的是,在这两种情况下,每次查询时都必须检查每个配方文档,如果您的集合变大,这将对性能产生严重影响。
使用关系数据库可以更好地处理这个问题。至少,您应该考虑保留一个单独的可查询的成分索引,该索引具有使用它们的配方ID的映射。然后你可以只检索你拥有的成分,并在代码中采用配方的交集。同样,由于这实际上是关系数据库可以为您做的事情,因此它可能只是更好的选择。
答案 2 :(得分:0)
您可以使用dot notation访问数组的特定元素,并检查每个元素是$in
您的可用成分列表,并且您可以构造$and
查询来为每个元素执行此操作在被查询的文档中当然无法正常工作,因为您不知道每个食谱的成分列表中有多少元素。
该问题的一个解决方案是为每个文档添加一个固定大小(可能是3个元素)的数组,在其中放入来自配方的n个最不常见的成分,填充(如果需要,可以重复填充数组) )。现在,您可以使用$and
和$in
对该数组的每个元素进行查询。这将找到“可能”可能的所有食谱,并且由于您存储了最少见的成分,因此应该很好地过滤掉您无法制作的食谱。然后,您可以在客户端进行最终过滤。
有时添加不同的数据表示可能是解决像这样棘手的查询问题的好方法,有时服务器和客户端过滤的组合可能是合适的。
答案 3 :(得分:0)
您可以使用javascript函数作为查找参数。但是,由于可扩展性以及将用户输入插入到执行代码中所带来的普遍不安,我认为MapReduce将是一个更优雅的解决方案。
以下是将要构建的示例查询。基本上,它过滤项目的成分并检查过滤后的列表与原始成分列表的长度是否相同:
db.shakes.find(function() {
// Implant your query JSON object here
var query_obj = [{"type" : "fruit", "name" : "mango"},
{"type" : "milk", "name" : "soy milk"},
{"type" : "fruit", "name" : "strawberry"}];
return this.ingredients.filter(function(ingredient) {
var has_ingredients = false;
query_obj.forEach(function(query) {
has_ingredients |= (query.type == ingredient.type && query.name == ingredient.name);
});
return has_ingredients;
}).length === this.ingredients.length;
})
答案 4 :(得分:0)
没有直接的方法可以做到这一点。你可以通过查看每个文档来构建一个简单的javascript函数来解决你的查询,并检查数组成分有三种成分。
db.shakes.find().forEach(
function (shakes) {
for (i = 0; i < shakes.ingredients.length; i++) {
switch (shakes.ingredients[i].name) {
case "banana":
var count = 1;
case "soy milk":
count++;
case "mango":
count++;
}
if (count == 3) {
print(shakes.name);
}
}
}
);
答案 5 :(得分:0)
为了带来一些新的想法,如果你有一套已知的所有成分。例如,可能的成分只有5:
["mango","soy milk", "strawberry", "banana", "pineapple"]
而且你既没有香蕉也没有菠萝
一种选择是查询您没有的成分:
{"ingredients" : {$nin : [{"type" : "fruit", "name" : "banana"},
{"type" : "fruit", "name" : "pineapple"}]}}
这将返回所有没有您没有的成分的文件,因此,您可以获得实际拥有的成分。
答案 6 :(得分:0)
假设您对caveats of using the $where
operator没问题,可以将其与Array.prototype.some和Array.prototype.every结合使用,以获得所需的结果。
这看起来像是一个特定于JavaScript的解决方案,但事实并非如此。 javascript直接注入Mongo(V8)。所有语言驱动程序都应支持它。我至少知道PHP does。
您应该可以将此代码直接粘贴到Mongo REPL中,并获得您期望的结果(敲木头)。
如果您的奶昔数据库有可能超出$where
所能提供的范围,Aggregation Framework就是您的朋友。
var query = {
"$where": function() {
var myIngredients = [
{"type" : "fruit", "name" : "mango"},
{"type" : "milk", "name" : "soy milk"},
{"type" : "fruit", "name" : "strawberry"}
];
return obj.ingredients.every(function(milkShakeIngredient){
return myIngredients.some(function(myIngredient){
return ( milkShakeIngredient.name === myIngredient.name && milkShakeIngredient.type === myIngredient.type );
});
});
}
};
db.milkShakes.find(query);
答案 7 :(得分:0)
您可以为此使用聚合管道。
使用$addFields创建一个missing
元素,其中包含缺少的成分($setDifference将帮助您获取丢失的成分)以适应当前的抖动。然后使用$match过滤掉所有没有成分的奶昔。
查询示例:
db.getCollection('shakes').aggregate([
{
$addFields: {
'missing': {
$setDifference: [
"$ingredients",
[
{"type" : "fruit", "name" : "mango"},
{"type" : "milk", "name" : "soy milk"},
{"type" : "fruit", "name" : "strawberry"}
]
]
}
}
},
{ $match: {'missing': []} }
])