Разделение данных вокруг запроса на совпадение во время агрегации - PullRequest
0 голосов
/ 18 февраля 2020

Я пытался разобраться в том, чтобы выполнить какое-то разбиение (разбитое по предикату) в запросе mon go. Мой текущий запрос выглядит так:

db.posts.aggregate([
 {"$match": { $and:[ {$or:[{"toggled":false},{"toggled":true, "status":"INACTIVE"}]}  ,  {"updatedAt":{$gte:1549786260000}} ] }},
 {"$unwind" :"$interests"},
 {"$group" : {"_id": {"iid": "$interests", "pid":"$publisher"}, "count": {"$sum" : 1}}},
 {"$project":{ _id: 0, "iid": "$_id.iid", "pid": "$_id.pid", "count": 1 }}
])

Это приводит к следующему выводу:

{
    "count" : 3.0,
    "iid" : "INT456",
    "pid" : "P789"
}
{
    "count" : 2.0,
    "iid" : "INT789",
    "pid" : "P789"
}
{
    "count" : 1.0,
    "iid" : "INT123",
    "pid" : "P789"
}
{
    "count" : 1.0,
    "iid" : "INT123",
    "pid" : "P123"
}

Пока все хорошо, но потом я понял, что для документов, которые соответствуют Специфический c фильтр {"toggled":true, "status":"INACTIVE"}, я бы предпочел уменьшить счетчик (-1) . (учитывая, что конечное значение также может быть отрицательным.)

Есть ли способ каким-либо образом разделить данные после match, чтобы убедиться, что различные операции grouping выполняются для обеих коллекций документов?

Что-то похожее на то, что я ищу, это $mergeObjects, или, может быть, $reduce, но не так много, как я могу рассказать из примеров документации.

Примечание : Я чувствую, что одним простым способом решения этой проблемы является выполнение двух запросов, но я ищу один запрос для выполнения операции.


Образцы документов для вышеприведенного вывода будут иметь следующий вид:

/* 1 */
{
    "_id" : ObjectId("5d1f7******"),
    "id" : "CON123",
    "title" : "Game",
    "content" : {},
    "status" : "ACTIVE",
    "toggle":false,
    "publisher" : "P789",
    "interests" : [ 
        "INT456"
    ],
    "updatedAt" : NumberLong(1582078628264)
}

/* 2 */
{
    "_id" : ObjectId("5d1f8******"),
    "id" : "CON456",
    "title" : "Home",
    "content" : {},
    "status" : "INACTIVE",
    "toggle":true,
    "publisher" : "P789",
    "interests" : [ 
        "INT456",
        "INT789"
    ],
    "updatedAt" : NumberLong(1582078628264)
}

/* 3 */
{
    "_id" : ObjectId("5d0e9******"),
    "id" : "CON654",
    "title" : "School",
    "content" : {},
    "status" : "ACTIVE",
    "toggle":false,
    "publisher" : "P789",
    "interests" : [ 
        "INT123",
        "INT456",
        "INT789"
    ],
    "updatedAt" : NumberLong(1582078628264)
}

/* 4 */
{
    "_id" : ObjectId("5d207*******"),
    "id" : "CON789",
    "title":"Stack",
    "content" : { },
    "status" : "ACTIVE",
    "toggle":false,
    "publisher" : "P123",
    "interests" : [ 
        "INT123"
    ],
    "updatedAt" : NumberLong(1582078628264)
}

В результате я с нетерпением ожидаю

{
    "count" : 1.0, (2-1)
    "iid" : "INT456",
    "pid" : "P789"
}
{
    "count" : 0.0, (1-1)
    "iid" : "INT789",
    "pid" : "P789"
}
{
    "count" : 1.0,
    "iid" : "INT123",
    "pid" : "P789"
}
{
    "count" : 1.0,
    "iid" : "INT123",
    "pid" : "P123"
}

1 Ответ

1 голос
/ 19 февраля 2020

Эта агрегация дает желаемый результат.

db.posts.aggregate( [
{ $match:  { updatedAt: { $gte: 1549786260000 } } },
{ $facet: {
        FALSE: [
            { $match: { toggle: false } },
            { $unwind : "$interests" },
            { $group : { _id : { iid: "$interests", pid: "$publisher" }, count: { $sum : 1 } } },
        ],
        TRUE: [
            { $match: { toggle: true, status: "INACTIVE" } },
            { $unwind : "$interests" },
            { $group : { _id : { iid: "$interests", pid: "$publisher" }, count: { $sum : -1 } } },
        ]
} },
{ $project: { result: { $concatArrays: [ "$FALSE", "$TRUE" ] } } },
{ $unwind: "$result" },
{ $replaceRoot: { newRoot: "$result" } },
{ $group : { _id : "$_id", count: { $sum : "$count" } } },
{ $project:{ _id: 0, iid: "$_id.iid", pid: "$_id.pid", count: 1 } }
] )


[РЕДАКТИРОВАТЬ ДОБАВИТЬ]

Вывод запроса с использованием входных данных из поста вопроса:

{ "count" : 1, "iid" : "INT123", "pid" : "P789" }
{ "count" : 1, "iid" : "INT123", "pid" : "P123" }
{ "count" : 0, "iid" : "INT789", "pid" : "P789" }
{ "count" : 1, "iid" : "INT456", "pid" : "P789" }



[РЕДАКТИРОВАТЬ, ДОБАВИТЬ 2]

Этот запрос получает тот же результат с другим подходом (код):

db.posts.aggregate( [
  { 
      $match:  { updatedAt: { $gte: 1549786260000 } } 
  },
  { 
      $unwind : "$interests" 
  },
  { 
      $group : { 
          _id : { 
              iid: "$interests", 
              pid: "$publisher" 
          }, 
          count: { 
              $sum: {
                  $switch: {
                      branches: [
                        { case: { $eq: [ "$toggle", false ] },
                           then: 1 },
                        { case: { $and: [ { $eq: [ "$toggle", true] },  { $eq: [ "$status", "INACTIVE" ] } ] },
                           then: -1 }
                      ]
                  }          
              } 
          }
      } 
  },
  { 
      $project:{
           _id: 0, 
           iid: "$_id.iid", 
           pid: "$_id.pid", 
           count: 1 
      } 
  }
] )


[РЕДАКТИРОВАТЬ ДОБАВИТЬ 3]

ПРИМЕЧАНИЕ:

Фасетный запрос запускает два фасета (TRUE и FALSE) для одного и того же набора документов; это как два запроса, работающие параллельно. Но есть некоторое дублирование кода, а также дополнительные этапы формирования документов по конвейеру для получения желаемого результата.

Второй запрос позволяет избежать дублирования кода, и в конвейере агрегации гораздо меньше этапов. Это будет иметь значение, если во входном наборе данных есть большое количество документов для обработки - с точки зрения производительности. Как правило, меньшие этапы означают меньшие итерации документов (поскольку этап должен сканировать документы, которые выводятся на предыдущем этапе).

...