MongoDB: агрегация/группировка голосов в опросах

Пытаюсь создать агрегацию результатов опроса

у меня две коллекции

опрос - вот один документ

{
  "_id": {
    "$oid": "636027704f7a15587ef74f26"
  },
  "question": "question 1",
  "ended": false,
  "options": [
    {
      "id": "1",
      "option": "option 1"
    },
    {
      "id": "2",
      "option": "option 2"
    },
    {
      "id": "3",
      "option": "option 3"
    }
  ]
}

Голосовать - вот один документ

{
  "_id": {
    "$oid": "635ed3210acbf9fd14af8fd1"
  },
  "poll_id": "636027704f7a15587ef74f26",
  "poll_option_id": "1",
  "user_id": "1"
}

и я хочу выполнить агрегированный запрос, чтобы получить результаты опроса

поэтому я делаю следующий запрос


db.vote.aggregate(
    [
        {
            $addFields: {
                poll_id: { "$toObjectId": "$poll_id" }
            },
        },
        {
            $lookup: {
                from: "poll",
                localField: "poll_id",
                foreignField: "_id",
                as: "details"
            }
        },
        {
            $group:
            {
                _id: { poll_id: "$poll_id", poll_option_id: "$poll_option_id" },
                details: { $first: "$details" },
                count: { $sum: 1 }
            }
        },
        {
            $addFields: {
                question: { $arrayElemAt: ["$details.question", 0] }
            }
        },
        {
            $addFields: {
                options: { $arrayElemAt: ["$details.options", 0] }
            }
        },
        {
            $group: {
                _id: "$_id.poll_id",
                poll_id: { $first: "$_id.poll_id" },
                question: { $first: "$question" },
                options: { $first: "$options" },
                optionsGrouped: {
                    $push: {
                        id: "$_id.poll_option_id",
                        count: "$count"
                    }
                },
                count: { $sum: "$count" }
            }
        }
    ]
)

Это дает мне эту форму результатов

{ _id: ObjectId("636027704f7a15587ef74f26"),
  poll_id: ObjectId("636027704f7a15587ef74f26"),
  question: 'question 1',
  options: 
   [ { id: '1', option: 'option 1' },
     { id: '2', option: 'option 2' },
     { id: '3', option: 'option 3' } ],
  optionsGrouped: 
   [ { id: '1', count: 2 },
     { id: '2', count: 1 } ],
  count: 3 }

Итак, что меня интересует, я хочу, чтобы результаты выглядели так (например, объединение обоих параметров и группы параметров)

{ _id: ObjectId("636027704f7a15587ef74f26"),
  poll_id: ObjectId("636027704f7a15587ef74f26"),
  question: 'question 1',
  optionsGrouped: 
   [ { id: '1', option: 'option 1', count: 2 },
     { id: '2', option: 'option 2', count: 1 },
     { id: '3', option: 'option 3', count: 0 } ],
  count: 4 }

Другой вопрос, является ли структура БД приемлемой в целом, или я могу представить это лучше?

Использование JavaScript и MongoDB
Использование JavaScript и MongoDB
Сегодня я собираюсь вкратце рассказать о прототипах в JavaScript, а также представить и объяснить вам работу с базой данных MongoDB.
1
0
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Один из вариантов — сначала сгруппировать, а затем использовать $lookup, чтобы получить меньше данных из коллекции poll. После $lookup используйте $map с $cond для объединения массивов:

db.vote.aggregate([
  {$group: {
      _id: {poll_id: {$toObjectId: "$poll_id"}, poll_option_id: "$poll_option_id"},
      count: {$sum: 1}
  }},
  {$group: {
      _id: "$_id.poll_id",
      counts: {
        $push: {count: "$count", option: {$concat: ["option ", "$_id.poll_option_id"]}}
      },
      countAll: {$sum: "$count"}
  }},
  {$lookup: {
      from: "poll",
      localField: "_id",
      foreignField: "_id",
      as: "poll"
  }},
  {$project: {poll: {$first: "$poll"}, counts: 1, countAll: 1}},
  {$project: {
      optionsGrouped: {
        $map: {
          input: "$poll.options",
          in: {$mergeObjects: [
              "$$this",
              {$cond: [
                  {$gte: [{$indexOfArray: ["$counts.option", "$$this.option"]}, 0]},
                  {$arrayElemAt: ["$counts", {$indexOfArray: ["$counts.option", "$$this.option"]}]},
                  {count: 0}
              ]}
          ]}
        }
      },
      count: "$countAll",
      question: "$poll.question"
  }}
])

Посмотрите, как это работает на примере детской площадки

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

Sherif Mo Shalaby 03.11.2022 13:39

Я не уверен, что полностью понимаю ваш запрос, пожалуйста, предоставьте образцы документов и запрошенные результаты, и я рассмотрю их.

nimrod serok 03.11.2022 13:50

поэтому я делаю запрос от пользователя (приложение Frontend), который хочет увидеть все эти опросы, и мне нужно отметить вариант, который он выбрал для каждого опроса, если он уже сделал это, если вы можете увидеть там в документе для голосования, который у меня есть user_id, поэтому, учитывая входной идентификатор пользователя, есть ли способ сопоставить истинное значение в случае, если данный user_id == option.user_id, SO в конце будет выглядеть как ``` [ { "_id": ObjectId("636027704f7a15587ef74f26"), "count": 3, "optionsGrouped": [ { "count": 2 и т. д.. "checked": "true", } ], "question": "вопрос 1" } ]```

Sherif Mo Shalaby 03.11.2022 14:30

В этом случае используйте это

nimrod serok 03.11.2022 16:28

я внес некоторые изменения в запрос, и все хорошо, только один сложный случай, когда голосов нет, опрос вообще не возвращается, поскольку $lookup выполняет левое соединение, поэтому, скорее всего, мне нужно будет запросить коллекцию опросов, чтобы выполнить соединение затем поступит по той же логике; скорее всего с этого направления будет не просто, попробую и выложу сюда соль если получилось

Sherif Mo Shalaby 03.11.2022 21:23

я пытался инвертировать соединения, но проблема в том, что агрегация теперь находится на втором уровне после соединения, что меня немного сбивает с толку; у вас есть идеи, что нужно сделать, я просто хочу получить все опросы, даже если у них нет голосов, в настоящее время я получаю только те, у которых есть голоса, извините, чувак, исходящие из реляционной базы данных, поэтому термины и то, как выполняются запросы, здесь отличается и смущает меня

Sherif Mo Shalaby 08.11.2022 13:20

Я переработал запрос, чтобы он соответствовал моим желаниям

и этот запрос отвечает на вопрос, который я задал

db.poll.aggregate([
  {
    $addFields: {
      _id: {
        $toString: "$_id"
      }
    }
  },
  {
    $lookup: {
      from: "poll_vote",
      localField: "_id",
      foreignField: "poll_id",
      as: "votes"
    }
  },
  {
    $replaceRoot: {
      newRoot: {
        $let: {
          vars: {
            count: {
              $size: "$votes"
            },
            options: {
              $map: {
                input: "$options",
                as: "option",
                in: {
                  $mergeObjects: [
                    "$$option",
                    {
                      count: {
                        $size: {
                          $slice: [
                            {
                              $filter: {
                                input: "$votes",
                                as: "v",
                                cond: {
                                  $and: [
                                    {
                                      $eq: [
                                        "$$v.poll_option_id",
                                        "$$option._id"
                                      ]
                                    }
                                  ]
                                }
                              }
                            },
                            0,
                            100
                          ]
                        }
                      }
                    },
                    {
                      checked: {
                        $toBool: {
                          $size: {
                            $slice: [
                              {
                                $filter: {
                                  input: "$votes",
                                  as: "v",
                                  cond: {
                                    $and: [
                                      {
                                        $eq: [
                                          "$$v.user_id",
                                          2
                                        ]
                                      },
                                      {
                                        $eq: [
                                          "$$v.poll_option_id",
                                          "$$option._id"
                                        ]
                                      }
                                    ]
                                  }
                                }
                              },
                              0,
                              100
                            ]
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "in": {
            _id: "$_id",
            question: "$question",
            count: "$$count",
            ended: "$ended",
            options: "$$options"
          }
        }
      }
    }
  },
  {
    $addFields: {
      answered: {
        $reduce: {
          input: "$options",
          initialValue: false,
          in: {
            $cond: [
              {
                $eq: [
                  "$$this.checked",
                  true
                ]
              },
              true,
              "$$value"
            ]
          }
        }
      }
    }
  }
])

Другие вопросы по теме