Как сделать агрегирование пимонго с подсчетом всех элементов и группировкой по одному запросу - PullRequest
1 голос
/ 05 июня 2019

У меня есть коллекция с полями, подобными этим:

{
    "_id":"5cf54857bbc85fd0ff5640ba",
    "book_id":"5cf172220fb516f706d00591",
    "tags":{
        "person":[
            {"start_match":209, "length_match":6, "word":"kimmel"}
        ],
        "organization":[
            {"start_match":107, "length_match":12, "word":"philadelphia"},
            {"start_match":209, "length_match":13, "word":"kimmel center"}
        ],
        "location":[
            {"start_match":107, "length_match":12, "word":"philadelphia"}
        ]
    },
    "deleted":false
}

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

{
    "response": [
        {
            "tag": "location",
            "tag_list": [
                {
                    "count": 31,
                    "phrase": "philadelphia"
                },
                {
                    "count": 15,
                    "phrase": "usa"
                }
             ]
        },
        {
            "tag": "organization",
            "tag_list": [ ... ]
        },
        {
            "tag": "person",
            "tag_list": [ ... ]
        },
    ]
}

конвейер, как это работает:

def pipeline_func(tag):
    return [
        {'$replaceRoot': {'newRoot': '$tags'}},
        {'$unwind': '${}'.format(tag)},
        {'$group': {'_id': '${}.word'.format(tag), 'count': {'$sum': 1}}},
        {'$project': {'phrase': '$_id', 'count': 1, '_id': 0}},
        {'$sort': {'count': -1}}
    ]

Но он делает запрос для каждого тега. Я хочу знать, как сделать это за один запрос. Спасибо за внимание.

1 Ответ

1 голос
/ 05 июня 2019

Как уже отмечалось, незначительное несоответствие в данных вопроса текущему заявленному конвейерному процессу, поскольку $unwind может только использоваться на массивы и tags, как указано в вопросе, не массив .

Для данных, представленных в вопросе, вы в основном хотите конвейер, подобный этому:

db.collection.aggregate([
  { "$addFields": {
    "tags": { "$objectToArray": "$tags" }
  }},
  { "$unwind": "$tags" },
  { "$unwind": "$tags.v" },
  { "$group": {
    "_id": {
      "tag": "$tags.k",
      "phrase": "$tags.v.word"
    },
    "count": { "$sum": 1 }
  }},
  { "$group": {
    "_id": "$_id.tag",
    "tag_list": {
      "$push": {
        "count": "$count",
        "phrase": "$_id.phrase"
      }
    }
  }}
])

Опять же, согласно примечанию, поскольку tags на самом деле является объектом , то, что вам действительно нужно для сбора данных на основе его субключей , поскольку вопрос спрашивать, это превратить это по существу в массив элементов.

Использование $replaceRoot в вашем текущем конвейере, по-видимому, указывает на то, что $objectToArray имеет правильного использования здесь, так как оно доступно из более поздние патчи MongoDB 3.4, являющиеся минимальной версией, которую вы должны использовать в работе прямо сейчас.

То, что $objectToArray на самом деле в значительной степени выполняет то, что говорит имя, и создает массив (или «список», чтобы быть более pythonic ) записей, разбитых на ключ и значение пар. По сути, это «список» объектов (или «dict» записей), которые имеют ключи k и v соответственно. Вывод первого этапа конвейера будет выглядеть следующим образом в поставляемом документе:

{
  "book_id": "5cf172220fb516f706d00591",
  "tags": [
    {
      "k": "person",
      "v": [
        {
          "start_match": 209,
          "length_match": 6,
          "word": "kimmel"
        }
      ]
    }, {
      "k": "organization",
      "v": [
        {
          "start_match": 107,
          "length_match": 12,
          "word": "philadelphia"
        }, {
          "start_match": 209,
          "length_match": 13,
          "word": "kimmel center"
        }
      ]
    }, {
      "k": "location",
      "v": [
        {
          "start_match": 107,
          "length_match": 12,
          "word": "philadelphia"
        }
      ]
    }
  ],
  "deleted" : false
}

Таким образом, вы сможете увидеть, как теперь вы можете легко получить доступ к этим k значениям и использовать их в группировке , и, конечно, v также является стандартным массивом. Так что это просто две $unwind ступени, как показано, а затем две $group ступени. Будучи первым $group для сбора по комбинации клавиш, а вторым собираем по основному ключу группировки и добавляем другие накопления к " list " в этой записи.

Конечно, вывод из приведенного выше списка не точно , как вы просили в вопросе, но данные в основном там. При желании можно добавить этап $addFields или $project, чтобы по существу переименовать ключ _id в качестве конечного этапа агрегирования:

  { "$addFields": {
    "_id": "$$REMOVE",
    "tag": "$_id"
  }}

Или просто сделать что-то pythonic с небольшим пониманием списка на выходе курсора:

cursor = db.collection.aggregate([
  { "$addFields": {
    "tags": { "$objectToArray": "$tags" }
  }},
  { "$unwind": "$tags" },
  { "$unwind": "$tags.v" },
  { "$group": {
    "_id": {
      "tag": "$tags.k",
      "phrase": "$tags.v.word"
    },
    "count": { "$sum": 1 }
  }},
  { "$group": {
    "_id": "$_id.tag",
    "tag_list": {
      "$push": {
        "count": "$count",
        "phrase": "$_id.phrase"
      }
    }
  }}
])

output = [{ 'tag': doc['_id'], 'tag_list': doc['tag_list'] } for doc in cursor]

print({ 'response': output });

И окончательный вывод в виде "списка" , который вы можете использовать для response:

{
  "tag_list": [
    {
      "count": 1,
      "phrase": "philadelphia"
    }
  ],
  "tag": "location"
},
{
  "tag_list": [
    {
      "count": 1,
      "phrase": "kimmel"
    }
  ],
  "tag": "person"
},
{
  "tag_list": [
    {
      "count": 1,
      "phrase": "kimmel center"
    }, {
      "count": 1,
      "phrase": "philadelphia"
    }
  ],
  "tag": "organization"
}

Отметив, что при использовании подхода list у вас есть немного больший контроль над порядком "ключей" в качестве выходных данных, поскольку сама MongoDB просто добавляет новые имена ключей в проекции сохраняя существующие ключи в порядке. Если такие вещи важны для вас, это так. Хотя это действительно не должно быть, так как все структуры типа Object / Dict не должны рассматриваться как имеющие какой-либо установленный порядок ключей. Для этого предназначены массивы (или списки).

...