Для того, что вы пытаетесь сделать, использование поиска $text
или даже $regex
является неправильным подходом. Все, что они могут сделать, это вернуть «совпадающие» документы только из коллекции.
Использование Aggregate для подсчета совпадений строк
Вист не столь гибок, как регулярное выражение (и, к сожалению, в настоящее время нет эквивалента оператора агрегирования, но он будет в будущих выпусках. См. SERVER-11947 ), лучше использовать $indexOfCP
, чтобы сопоставить вхождение «строки», а затем сосчитать их по «общему количеству» в каждой группе:
db.advertisements.aggregate([
{ "$group": {
"_id": "$metadata.brand",
"totalCount": { "$sum": 1 },
"matchedCount": {
"$sum": {
"$cond": [{ "$ne": [{ "$indexOfCP": [ "$metadata.model", "Negotiable" ] }, -1 ] }, 1, 0]
}
}
}},
{ "$addFields": {
"percentage": {
"$cond": {
"if": { "$ne": [ "$matchedCount", 0 ] },
"then": {
"$multiply": [
{ "$divide": [ "$matchedCount", "$totalCount" ] },
100
]
},
"else": 0
}
}
}},
{ "$sort": { "percentage": -1 } }
])
И результаты:
{ "_id" : "Siamoto", "totalCount" : 1, "matchedCount" : 1, "percentage" : 100 }
{ "_id" : "BMW", "totalCount" : 1, "matchedCount" : 0, "percentage" : 0 }
Обратите внимание, что $group
используется для накопления как всех документов, найденных в "brand"
, так и тех, в которых строка была сопоставлена. Используемый здесь оператор $cond
является «троичным» или if/then/else
оператором, который вычисляет логическое выражение и затем возвращает либо одно значение где true
, либо другое, где false
. В этом случае $indexOfCP
НЕ возвращает значение -1
или "not found".
«Процент» фактически выполняется на отдельном этапе, который в этом случае мы используем $addFields
, чтобы добавить «дополнительное поле». Операция в основном представляет собой $divide
над двумя накопленными значениями из предыдущего этапа. $cond
просто применяется, чтобы избежать ошибок «деления на 0», а $multiply
просто перемещает десятичные разряды в нечто, похожее на «процент». Но основная предпосылка состоит в том, что такие расчеты, которые требуют накопления «итогов» вначале, всегда будут манипуляцией на «более поздней стадии».
MongoDB 4.2 (предлагается) Предварительный просмотр
К вашему сведению, по текущему "нефинализированному" синтаксису для $regexFind
из MongoDB 4.2 (предложено, и еще не будет завершено, если оно будет включено в этот выпуск) и далее это будет примерно так:
db.advertisements.aggregate([
{ "$group": {
"_id": "$metadata.brand",
"totalCount": { "$sum": 1 },
"matchedCount": {
"$sum": {
"$cond": {
"if": {
"$ne": [
{ "$regexFind": {
"input": "$metadata.model",
"regex": /Negotiable/i
}},
null
]
},
"then": 1,
"else": 0
}
}
}
}},
{ "$addFields": {
"percentage": {
"$cond": {
"if": { "$ne": [ "$matchedCount", 0 ] },
"then": {
"$multiply": [
{ "$divide": [ "$matchedCount", "$totalCount" ] },
100
]
},
"else": 0
}
}
}},
{ "$sort": { "percentage": -1 } }
])
Еще раз решительно отмечая, что «текущая» реализация может быть изменена ко времени ее выпуска. Вот как это работает в текущей версии разработки 4.1.9-17-g0a856820ba
.
Использование MapReduce
Альтернативный подход, при котором ваша версия MongoDB не поддерживает $indexOfCP
ИЛИ вам нужно больше гибкости в том, как вы "сопоставляете строку", заключается в использовании вместо агрегирования mapReduce
:
db.advertisements.mapReduce(
function() {
emit(this.metadata.brand, {
totalCount: 1,
matchedCount: (/Negotiable/i.test(this.metadata.model)) ? 1 : 0
});
},
function(key,values) {
var obj = { totalCount: 0, matchedCount: 0 };
values.forEach(value => {
obj.totalCount += value.totalCount;
obj.matchedCount += value.matchedCount;
});
return obj;
},
{
"out": { "inline": 1 },
"finalize": function(key,value) {
value.percentage = (value.matchedCount != 0)
? (value.matchedCount / value.totalCount) * 100
: 0;
return value;
}
}
)
Это дает аналогичный результат, но очень специфичным для mapReduce способом:
{
"_id" : "BMW",
"value" : {
"totalCount" : 1,
"matchedCount" : 0,
"percentage" : 0
}
},
{
"_id" : "Siamoto",
"value" : {
"totalCount" : 1,
"matchedCount" : 1,
"percentage" : 100
}
}
Логика почти такая же. Мы «излучаем», используя «ключ» для "brand"
, а затем используем другую троичную , чтобы определить, считать ли «совпадение» или нет. В этом случае используется регулярное выражение test()
, и даже в качестве примера используется сопоставление без учета регистра.
Часть «редуктор» просто накапливает выданные значения, а функция finalize
- это то, где «процент» возвращается тем же процессом деления и умножения.
Основное различие между двумя отличными от основных способностями состоит в том, что mapReduce
не может делать "дальнейших действий", кроме накопления и базовых манипуляций в finalize
. «Сортировка», продемонстрированная в конвейере агрегации, не может быть выполнена с mapReduce
без вывода в отдельную коллекцию и выполнения отдельных find()
и sort()
для тех документов, которые содержатся.
В любом случае работает, и это зависит только от ваших потребностей и возможностей того, что у вас есть. Конечно, любой aggregate()
подход будет намного быстрее , чем при использовании JavaScript
оценки mapReduce
. Так что вы, вероятно, хотите aggregate()
в качестве предпочтения, где это возможно.