Агрегировать по тегам regex

#regex #mongodb #aggregation-framework

#регулярное выражение #mongodb #агрегация-фреймворк

Вопрос:

Я ищу способ подсчитать количество тегов, которые существуют для документов.

Данные выглядят следующим образом:

 [
    {
        "_id": ObjectId("...."),
        "tags": ["active-adult", "active-tradeout"]
    },
    {
        "_id": ObjectId("...."),
        "tags": ["active-child", "active-tradeout", "active-junk-tag"]
    },
    {
        "_id": ObjectId("...."),
        "tags": ["inactive-adult"]
    }
]
  

Вот как я хотел бы, чтобы результат агрегации выглядел:

 [
    {
        "_id": "active",
        "total": 2,
        "subtags": {
            "adult": 1,
            "child": 1,
            "tradeout": 2,
            "junk-tag": 1
        }
    },
    {
        "_id": "inactive",
        "total": 1,
        "subtags": {
            "adult": 1
        }
    }
]
  

Я знаю, что могу посчитать теги, но я ищу регулярное выражение

 db.User.aggregate([
    {$unwind: "$tags"},
    {$group: {_id: "$tags", total: {$sum: 1}}}
])
  

Комментарии:

1. Я бы не стал использовать регулярные выражения для встроенных структур, таких как таблицы стилей или XML-файлы. Вместо этого используйте алгоритм или, по крайней мере, программу GREP, которая может работать с текстом за несколько шагов

2. Да, именно так я это и делал, но было любопытно, есть ли способ сделать это с помощью платформы агрегации.

Ответ №1:

Вы можете выполнить небольшую обработку строк с $substr $cond помощью операторов и, чтобы получить желаемый результат (нет необходимости в регулярных выражениях). Для этого потребуется MongoDB 2.6 :

 db.User.aggregate([
    { $unwind : "$tags"},
    { $project : { 
        tagType : { 
            $cond : { 
                if : { $eq : [ { $substr : [ "$tags", 0, 6] }, "active" ]}, 
                then: "active", 
                else: "inactive"}
            }, 
        tag: {
            $cond : { 
                if : { $eq : [ { $substr : [ "$tags", 0, 6] }, "active" ]}, 
                then: { $substr : ["$tags", 7, -1]}, 
                else: { $substr : ["$tags", 9, -1]}}
            }
    }},
    { $group : { _id : {tagType : "$tagType", tag: "$tag"} , 
                 total: { $sum: 1}}},
    { $group : { _id : "$_id.tagType", 
                subtags: { $push : {tag : "$_id.tag", total: "$total"}},
                total: { $sum : "$total"}}}
]);
  

Результатом этого запроса будет следующее:

 {
    "_id" : "inactive",
    "subtags" : [
        {
            "tag" : "adult",
            "total" : 1
        }
    ],
    "total" : 1
}
{
    "_id" : "active",
    "subtags" : [
        {
            "tag" : "junk-tag",
            "total" : 1
        },
        {
            "tag" : "child",
            "total" : 1
        },
        {
            "tag" : "tradeout",
            "total" : 2
        },
        {
            "tag" : "adult",
            "total" : 1
        }
    ],
    "total" : 5
}
  

Редактировать:

Я только что заметил, что итог в результате подсчитывает общее количество тегов, а не количество документов, в которых был хотя бы один активный тег. Этот запрос даст вам точный результат, который вы хотели, хотя и немного сложнее:

 db.User.aggregate([
    /* unwind so we can process each tag from the array */
    { $unwind : "$tags"},
    /* Remove the active/inactive strings from the tag values 
       and create a new value tagType */
    { $project : { 
        tagType : { 
            $cond : { 
                if : { $eq : [ { $substr : [ "$tags", 0, 6] }, "active" ]}, 
                then: "active", 
                else: "inactive"}
        }, 
        tag: {
            $cond : { 
                if : { $eq : [ { $substr : [ "$tags", 0, 6] }, "active" ]}, 
                then: { $substr : ["$tags", 7, -1]}, 
                else: { $substr : ["$tags", 9, -1]}}
        }
    }},
    /* Group the documents by tag type, so we can 
       find num. of docs by tag type (total) */
    { $group : { _id : "$tagType", 
                 tags :{ $push : "$tag"}, 
                 docId :{ $addToSet : "$_id"}}},
    /* project the values so we can get the 'total' for tag type */
    { $project : { tagType : "$_id", 
                   tags : 1, 
                   "docTotal": { $size : "$docId" }}},
    /* we must unwind to get total count for each tag */
    { $unwind : "$tags"}, 
    /* sum the tags by type and tag value */
    { $group : { _id : {tagType : "$tagType", tag: "$tags"} , 
                 total: { $sum: 1}, docTotal: {$first : "$docTotal"}}},
    /* finally group by tagType so we can get subtags */
    { $group : { _id : "$_id.tagType", 
                 subtags: { $push : {tag : "$_id.tag", total: "$total"}},
                 total: { $first : "$docTotal"}}}
]);
  

Комментарии:

1. Ах, я вижу, так что было бы невозможно запустить это для неизвестного количества корневых тегов (т. Е. ‘active’, ‘active2’)? Это не условие, а скорее вопрос.

2. Вероятно, следует отметить, что для этого требуется MongoDB 2.6

3. > Я только что заметил, что общее количество в результате подсчитывает общее количество тегов, а не количество документов На самом деле я искал количество документов с корневым тегом и вложенными тегами.

4. @RyanSchumacher этот подход был скорректирован в соответствии с вашим примером (потому что я думал, что у вас только два типа тегов), но, к сожалению, он не будет хорошо работать для неизвестного количества тегов.

5. @RyanSchumacher если вы можете каким-то образом предварительно обработать имена тегов (и, возможно, добавить «категорию» для каждого документа) при создании документа, тогда вы, вероятно, могли бы выполнить более простую агрегацию.