Я не знаю о лучшем, но есть несколько разных способов приблизиться к этому, и в зависимости от версии MongoDB у вас есть.
Не слишком уверен, что это ваше намерение или нет , но запрос, как показано, будет соответствовать первому примеру документа, поскольку, поскольку ваша логика реализована, вы соответствуете элементам в массиве этого документа, которые должны содержаться в массиве образцов.
Так что, если вы действительно хотели документ чтобы содержать все эти элементы, тогда оператор $all
был бы очевидным выбором:
db.collection.find({ "users.user": { "$all": [ 1, 5, 7 ] } })
Но работая с предположением о том, что ваша логика на самом деле предназначена, по крайней мере в соответствии с предложением вы можете «фильтровать» эти результаты, комбинируя с оператором $in
, чтобы на вашем было меньше документов $where
** условие в оценке 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;
});
}
})
И вы получаете индекс, хотя фактический отсканированный будет умножен на количество элементов в массивах из согласованного документа но еще лучше, чем без дополнительного фильтра.
Или, возможно, вы рассматриваете логическую абстракцию оператора $and
, используемого в комбинации с $or
и, возможно, оператор $size
в зависимости от ваших реальных условий массива:
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 }
]
})
Итак, это поколение всех возможных перестановок вашего условия соответствия, но опять-таки производительность, скорее всего, будет зависеть от вашей доступной установленной версии.
ПРИМЕЧАНИЕ. На самом деле полный сбой в этом случае, поскольку это делает что-то совершенно другое и фактически приводит к логическому $in
Альтернативы с каркасом агрегации, ваш пробег может отличаться от того, что наиболее эффективно из-за количества документов в вашей коллекции, одного подхода с MongoDB 2.6 и выше:
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"
}}
])
So этот подход использует некоторые недавно введенные операторы set , чтобы сравнить содержимое, хотя, конечно, вам нужно реструктурировать массив, чтобы сделать е. сравнение.
Как указывалось, в $setIsSubset
имеется прямой оператор, который делает эквивалент комбинированных операторов выше в одном операторе:
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"
}}
])
Или с другим подходом, все еще использующим оператор $size
от MongoDB 2.6:
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"
}}
])
Что, конечно, все еще можно сделать , хотя немного более длинные в версиях до 2.6:
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"
}}
])
Это обычно округляет разные способы, пробует их и видит, что лучше всего подходит для вас. По всей вероятности, простая комбинация $in
с вашей существующей формой, вероятно, будет лучшей. Но во всех случаях убедитесь, что у вас есть индекс, который можно выбрать:
db.collection.ensureIndex({ "users.user": 1 })
. Это даст вам лучшую производительность, если вы каким-то образом получаете доступ к этому, так как все примеры здесь.
Я был заинтригован этим, поэтому в конечном итоге наработал тестовый пример, чтобы узнать, что было лучше всего. Итак, сначала некоторые генерации тестовых данных:
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 документами в коллекции со случайными массивами от 1..10 в длине, содержащими случайные значения 1..0, я пришел к числу совпадений 430 документы (уменьшены с 7749 по совпадению $in
) со следующими результатами (avg):
$in
: 420 мс $size
: 395ms $setIsSubset
: 250 мс Отмечая, что по образцам, выполненным все, кроме последних двух, была дисперсия peak примерно на 100 мс быстрее, а последние два показали 220 мс ответ. Наибольшие вариации были в запросе JavaScript, который также показал результаты на 100 мс медленнее.
Но дело здесь в том, что касается аппаратного обеспечения, которое на моем ноутбуке под VM не особенно велико, но дает представление.
Таким образом, совокупность и, в частности, версия MongoDB 2.6.1 с установленными операторами явно выигрывают от производительности с дополнительным небольшим усилением, поступающим от $setIsSubset
как один оператор.
Это особенно интересно (как указано в методе, совместимом с 2.4) наибольшая стоимость этого процесса будет заключаться в заявлении $unwind
(более 100 мс), поэтому при выборе $in
, имеющем среднее значение около 32 мс, остальные этапы конвейера выполняются меньше чем 100 мс в среднем. Таким образом, это дает относительное представление об агрегации и производительности JavaScript.
Как только вы получите токен-носитель, возвращенный с вашего сервера, этот токен должен быть передан в заголовок HTTP Authorization
со значением Bearer <token_here>
для доступа к защищенным ресурсам на стороне сервера.
С Retrofit 2 вы можете предоставить HTTP-заголовок, например:
@GET("/api/endpoint")
fun getResource(@Header("AUTHORIZATION") value: String): Call<ResponseType>