Как использовать $ lookup для массива вложенных документов - PullRequest
1 голос
/ 04 марта 2020

У меня есть эти схемы:

const chatbots = new Schema({
  name: String,
  campaigns: [{
    name: String,
    channels: [{
      _id: String,
      name: String,
      budget: Number
    }]
  }]
});

const chatbotusers = new Schema({
  name: String,
  campaign_channel: String
})

И мне нужно получить список кампаний, где для каждого канала у меня есть общее количество ChatbotUsers. Примерно так:

[
  {
    "name": "Campaign #1",
    "channels": {
      "_id": "eyRyZ1gD0",
      "name": "Channel #1",
      "users": 10
    }
  },
  {
    "name": "Campaign #1",
    "channels": {
      "_id": "tsKH7WxE",
      "name": "Channel #2",
      "users": 4
    }
  }
]

Есть идеи?

Самое большое, что я получил, было примерно так:

{
  $lookup: {
    from: "chatbotusers",
    localField: "channels._id",
    foreignField: "campaign_channel",
    as: "users",
  }
},
{
  $project: {
    name: "$name",
    channels: {
      $map: {
        input: "$channels",
        as: "channel",
        in: {
          _id: "$$channel._id",
          name: "$$channel.name",
          users: { $size: "$users" },
        }
      }
    }
  }
}

Но это подводит итог пользователям кампании, а не Канал.

(Извините, если название вопроса не подходит, я даже не знаю, как правильно задать это)

1 Ответ

1 голос
/ 04 марта 2020

Вы можете попробовать этот запрос:

db.chatbots.aggregate([
    {
        $lookup: {
            from: "chatbotusers",
            localField: "campaigns.channels._id",
            foreignField: "campaign_channel",
            as: "users"
        }
    },
    {
        $addFields: {
            campaigns: {
                $map: {
                    input: "$campaigns",
                    as: "eachCampaign",
                    in: {
                        $mergeObjects: ['$$eachCampaign', {
                            channels:
                            {
                                $reduce: {
                                    input: "$$eachCampaign.channels",
                                    initialValue: [],
                                    in: {
                                        $concatArrays: [
                                            "$$value",
                                            [
                                                {
                                                    $mergeObjects: [
                                                        "$$this",
                                                        {
                                                            user: {
                                                                $size: {
                                                                    $filter: {
                                                                        input: "$users",
                                                                        as: "e",
                                                                        cond: {
                                                                            $eq: [
                                                                                "$$e.campaign_channel",
                                                                                "$$this._id"
                                                                            ]
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    ]
                                                }
                                            ]
                                        ]
                                    }
                                }
                            }
                        }]
                    }
                }
            }
        }
    },
    {
        $project: {
            users: 0
        }
    }
])

Примечание: Для этого может быть несколько способов, но в этом случае мы работаем над одним и тем же документом из chatbots коллекция, а не взрываются документы, выполняя $unwind, что может быть полезно, когда у вас огромный набор данных.

Тест: MongoDB-Playground

Этот запрос должен дать вам то, что нужно, но в любом случае, если он медленный или вы думаете, чтобы улучшить его, тогда здесь:

{
    user: {
        $size: {
            $filter: {
                input: "$users", as: "e",
                    cond: {
                    $eq: [
                        "$$e.campaign_channel",
                        "$$this._id"
                    ]
                }
            }
        }
    }
}

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

В качестве альтернативы вы можете использовать этот запрос, который не сохраняет исходную структуру do c (также нет документов в Вывод может быть больше, чем у вас в коллекции), но может делать то, что вам нужно:

db.chatbots.aggregate([
  {
    $unwind: "$campaigns"
  },
  {
    $unwind: "$campaigns.channels"
  },
  {
    $lookup: {
      from: "chatbotusers",
      localField: "campaigns.channels._id",
      foreignField: "campaign_channel",
      as: "users"
    }
  },
  {
    $addFields: {
      "channels": "$campaigns.channels",
      campaigns: "$campaigns.name"
    }
  },
  {
    $addFields: {
      "channels.users": {
        $size: "$users"
      }
    }
  },
  {
    $project: {
      users: 0
    }
  }
])

Тест: MongoDB-Playground

...