3 回答

TA貢獻(xiàn)1862條經(jīng)驗(yàn) 獲得超6個(gè)贊
您想要的查詢是這樣的:
db.collection.find({"users":{"$not":{"$elemMatch":{"user":{$nin:[1,5,7]}}}}})
這就是說,找到所有沒有元素不在列表1,5,7之外的文檔。

TA貢獻(xiàn)1810條經(jīng)驗(yàn) 獲得超5個(gè)贊
我不知道更好,但是有幾種不同的方法可以解決此問題,具體取決于您可用的MongoDB版本。
不太確定這是否符合您的意圖,但是所示查詢將與第一個(gè)文檔示例匹配,因?yàn)樵趯?shí)現(xiàn)邏輯時(shí),您正在匹配文檔數(shù)組中必須包含在樣本數(shù)組中的元素。
因此,如果您實(shí)際上希望文檔包含所有這些元素,那么$all操作員將是顯而易見的選擇:
db.collection.find({ "users.user": { "$all": [ 1, 5, 7 ] } })
但是,在假設(shè)您的邏輯實(shí)際上是預(yù)期的前提下,至少根據(jù)建議,您可以通過與$in運(yùn)算符組合來“過濾”這些結(jié)果,從而減少需要您處理的文檔$where**條件在評(píng)估的JavaScript中:
db.collection.find({
"users.user": { "$in": [ 1, 5, 7 ] },
"$where": function() {
var ids = [1, 5, 7];
return this.users.every(function(u) {
return ids.indexOf(u.user) !== -1;
});
}
})
盡管實(shí)際掃描的結(jié)果將與匹配文檔中數(shù)組中元素的數(shù)量相乘,但您會(huì)得到一個(gè)索引,但是與沒有附加過濾器相比,它仍然更好。
甚至可能是你考慮的邏輯抽象$and結(jié)合使用,運(yùn)營商$or也可能是$size根據(jù)您的實(shí)際情況數(shù)組操作:
db.collection.find({
"$or": [
{ "users.user": { "$all": [ 1, 5, 7 ] } },
{ "users.user": { "$all": [ 1, 5 ] } },
{ "users.user": { "$all": [ 1, 7 ] } },
{ "users": { "$size": 1 }, "users.user": 1 },
{ "users": { "$size": 1 }, "users.user": 5 },
{ "users": { "$size": 1 }, "users.user": 7 }
]
})
因此,這是匹配條件所有可能排列的產(chǎn)物,但是性能可能會(huì)根據(jù)可用的安裝版本而有所不同。
注意:實(shí)際上,在這種情況下完全失敗,因?yàn)檫@樣做完全不同,并且實(shí)際上導(dǎo)致邏輯上的失敗。$in
備選方案是使用聚合框架,這取決于收集中的文檔數(shù)量,MongoDB 2.6及更高版本的一種方法可能會(huì)影響哪種效率最高。
db.problem.aggregate([
// Match documents that "could" meet the conditions
{ "$match": {
"users.user": { "$in": [ 1, 5, 7 ] }
}},
// Keep your original document and a copy of the array
{ "$project": {
"_id": {
"_id": "$_id",
"date": "$date",
"users": "$users"
},
"users": 1,
}},
// Unwind the array copy
{ "$unwind": "$users" },
// Just keeping the "user" element value
{ "$group": {
"_id": "$_id",
"users": { "$push": "$users.user" }
}},
// Compare to see if all elements are a member of the desired match
{ "$project": {
"match": { "$setEquals": [
{ "$setIntersection": [ "$users", [ 1, 5, 7 ] ] },
"$users"
]}
}},
// Filter out any documents that did not match
{ "$match": { "match": true } },
// Return the original document form
{ "$project": {
"_id": "$_id._id",
"date": "$_id.date",
"users": "$_id.users"
}}
])
因此,該方法使用一些新引入的集合運(yùn)算符來比較內(nèi)容,但是當(dāng)然您需要重組數(shù)組才能進(jìn)行比較。
如所指出的,有一個(gè)直接的運(yùn)算符可以做到這一點(diǎn),$setIsSubset其中在單個(gè)運(yùn)算符中可以實(shí)現(xiàn)上述組合運(yùn)算符的等效功能:
db.collection.aggregate([
{ "$match": {
"users.user": { "$in": [ 1,5,7 ] }
}},
{ "$project": {
"_id": {
"_id": "$_id",
"date": "$date",
"users": "$users"
},
"users": 1,
}},
{ "$unwind": "$users" },
{ "$group": {
"_id": "$_id",
"users": { "$push": "$users.user" }
}},
{ "$project": {
"match": { "$setIsSubset": [ "$users", [ 1, 5, 7 ] ] }
}},
{ "$match": { "match": true } },
{ "$project": {
"_id": "$_id._id",
"date": "$_id.date",
"users": "$_id.users"
}}
])
或者采用另一種方法,同時(shí)仍然利用$sizeMongoDB 2.6 中的運(yùn)算符:
db.collection.aggregate([
// Match documents that "could" meet the conditions
{ "$match": {
"users.user": { "$in": [ 1, 5, 7 ] }
}},
// Keep your original document and a copy of the array
// and a note of it's current size
{ "$project": {
"_id": {
"_id": "$_id",
"date": "$date",
"users": "$users"
},
"users": 1,
"size": { "$size": "$users" }
}},
// Unwind the array copy
{ "$unwind": "$users" },
// Filter array contents that do not match
{ "$match": {
"users.user": { "$in": [ 1, 5, 7 ] }
}},
// Count the array elements that did match
{ "$group": {
"_id": "$_id",
"size": { "$first": "$size" },
"count": { "$sum": 1 }
}},
// Compare the original size to the matched count
{ "$project": {
"match": { "$eq": [ "$size", "$count" ] }
}},
// Filter out documents that were not the same
{ "$match": { "match": true } },
// Return the original document form
{ "$project": {
"_id": "$_id._id",
"date": "$_id.date",
"users": "$_id.users"
}}
])
當(dāng)然,哪一個(gè)仍然可以完成,盡管在2.6之前的版本中要花更長的時(shí)間:
db.collection.aggregate([
// Match documents that "could" meet the conditions
{ "$match": {
"users.user": { "$in": [ 1, 5, 7 ] }
}},
// Keep your original document and a copy of the array
{ "$project": {
"_id": {
"_id": "$_id",
"date": "$date",
"users": "$users"
},
"users": 1,
}},
// Unwind the array copy
{ "$unwind": "$users" },
// Group it back to get it's original size
{ "$group": {
"_id": "$_id",
"users": { "$push": "$users" },
"size": { "$sum": 1 }
}},
// Unwind the array copy again
{ "$unwind": "$users" },
// Filter array contents that do not match
{ "$match": {
"users.user": { "$in": [ 1, 5, 7 ] }
}},
// Count the array elements that did match
{ "$group": {
"_id": "$_id",
"size": { "$first": "$size" },
"count": { "$sum": 1 }
}},
// Compare the original size to the matched count
{ "$project": {
"match": { "$eq": [ "$size", "$count" ] }
}},
// Filter out documents that were not the same
{ "$match": { "match": true } },
// Return the original document form
{ "$project": {
"_id": "$_id._id",
"date": "$_id.date",
"users": "$_id.users"
}}
])
通常,這會(huì)找出不同的方法,嘗試一下,看看哪種方法最適合您。$in與您現(xiàn)有表單的簡單組合很可能是最好的組合。但是在所有情況下,請(qǐng)確保您具有可以選擇的索引:
db.collection.ensureIndex({ "users.user": 1 })
只要您以某種方式訪問它,這將為您提供最佳性能,如此處的所有示例所示。
判決
我對(duì)此很感興趣,因此最終設(shè)計(jì)了一個(gè)測(cè)試用例,以查看性能最佳的產(chǎn)品。因此,首先生成一些測(cè)試數(shù)據(jù):
var batch = [];
for ( var n = 1; n <= 10000; n++ ) {
var elements = Math.floor(Math.random(10)*10)+1;
var obj = { date: new Date(), users: [] };
for ( var x = 0; x < elements; x++ ) {
var user = Math.floor(Math.random(10)*10)+1,
group = Math.floor(Math.random(10)*10)+1;
obj.users.push({ user: user, group: group });
}
batch.push( obj );
if ( n % 500 == 0 ) {
db.problem.insert( batch );
batch = [];
}
}
集合中有10000個(gè)文檔,其中長度為1..10的隨機(jī)數(shù)組保持1..0的隨機(jī)值,我得出了430個(gè)文檔的匹配計(jì)數(shù)(從$inmatch的7749減少),結(jié)果如下(平均):
JavaScript with $in子句:420ms
總有$size:395ms
帶有組數(shù)組計(jì)數(shù)的聚合:650ms
包含兩個(gè)集合運(yùn)算符的集合:275ms
聚合時(shí)間$setIsSubset:250ms
請(qǐng)注意,除了最后兩個(gè)樣本外,所有樣本均完成了約100ms 的峰值方差,并且最后兩個(gè)樣本均顯示了220ms的響應(yīng)。最大的變化是在JavaScript查詢中,該查詢的結(jié)果也慢了100毫秒。
但是這里的要點(diǎn)是與硬件相關(guān)的,在我的筆記本電腦上的VM下,硬件并不是特別出色,但是可以提供一個(gè)思路。
因此,總體而言,特別是具有集合運(yùn)算符的MongoDB 2.6.1版本顯然會(huì)在性能上勝出,而$setIsSubset作為單個(gè)運(yùn)算符還會(huì)帶來一點(diǎn)額外收益。
鑒于(如2.4兼容方法所示),此過程中的最大開銷將是$unwind語句(超過100ms avg),因此,這特別有趣,因此,$in選擇的平均時(shí)間約為32ms,其余流水線階段將在不到100ms內(nèi)執(zhí)行一般。這樣就給出了聚合與JavaScript性能的相對(duì)概念。

TA貢獻(xiàn)1863條經(jīng)驗(yàn) 獲得超2個(gè)贊
我只是花了大部分時(shí)間試圖通過對(duì)象比較而不是嚴(yán)格的相等性來實(shí)現(xiàn)上述Asya的解決方案。所以我想在這里分享。
假設(shè)您將問題從userIds擴(kuò)展到了完整用戶。您想查找所有文檔,其中其users數(shù)組中的每個(gè)項(xiàng)目都出現(xiàn)在另一個(gè)用戶數(shù)組中:[{user: 1, group: 3}, {user: 2, group: 5},...]
這是行不通的:db.collection.find({"users":{"$not":{"$elemMatch":{"$nin":[{user: 1, group: 3},{user: 2, group: 5},...]}}}}})因?yàn)? nin僅適用于嚴(yán)格的平等。因此,我們需要找到一種不同的方式來表示對(duì)象數(shù)組的“不在數(shù)組中”。并且使用$where會(huì)大大降低查詢速度。
解:
db.collection.find({
"users": {
"$not": {
"$elemMatch": {
// if all of the OR-blocks are true, element is not in array
"$and": [{
// each OR-block == true if element != that user
"$or": [
"user": { "ne": 1 },
"group": { "ne": 3 }
]
}, {
"$or": [
"user": { "ne": 2 },
"group": { "ne": 5 }
]
}, {
// more users...
}]
}
}
}
})
完善邏輯:$ elemMatch匹配數(shù)組中沒有用戶的所有文檔。因此$ not將匹配數(shù)組中所有用戶的所有文檔。
- 3 回答
- 0 關(guān)注
- 1125 瀏覽
添加回答
舉報(bào)