【问题标题】:Count the distinct value of object in mongodb计算mongodb中对象的不同值
【发布时间】:2020-08-05 07:58:51
【问题描述】:

我是 mongoDB 的新手。我有两个集合 useranswer。 我需要统计每个问题的答案,并得到其中的百分比。答案集中总是只给出 3 个答案。

用户收藏

{
    _id : user1,
    questionId : 100,
    mail: "abc@gmail.com"
},

{
    _id : user2,
    questionId : 400,
    mail: "xyz@gmail.com"
}

答案集合

{ _id: 1,   userId : user1, answer1: "ok",  answer2: "bad",     answer3: "great"},
{ _id: 2,   userId : user1, answer1: "ok",  answer2: "ok",      answer3: "bad"  },
{ _id: 3,   userId : user2, answer1: "ok",  answer2: "Not good",answer3: "great"},
{ _id: 4,   userId : user2, answer1: "ok",  answer2: "bad",     answer3: "great"},
{ _id: 5,   userId : user2, answer1: "ok",  answer2: "bad",     answer3: "bad"  }

答案字段 answer1、answer2 和 answr3 可以具有不同或相同的值。根据这些值,我必须计算答案并在这 3 个答案中取百分比。

预期结果

[{
    _id:1,
    userId : user1,
    mail: "abc@gmail.com",
    ans:[
        {answer1 :[{"ok":2, percentage:100}]},
        {answer2 :[{"bad":1, percentage: 50},{"ok":1,percentage: 50}]},
        {answer3 :[{"great":1, percentage: 50},{"bad":1,percentage: 50}]}
    ]
},

{
    _id:1,
    userId : user2,
    mail: "xyz@gmail.com",
    ans:[
        {answer1 :[{"ok":3, percentage:100}]},
        {answer2 :[{"Not good":1, percentage: 33},{"bad":2,percentage: 66}]},
        {answer3 :[{"great":2, percentage: 66},{"bad":1,percentage: 33}]}
    ]
}]

我尝试如下,

[{
    $lookup: {
        from: 'answer',
        localField: '_id',
        foreignField: 'userId',
        as: 'join'
    }
}, {
    $unwind: {
        path: '$join',
        preserveNullAndEmptyArrays: true
    }
}, {
    $group: {
        _id: '$_id',
        answers: {
            $push: {
                A1: {
                    ans: "$join.answer1"
                },
                A2: {
                    ans: "$join.answer2"
                },
                A3: {
                    ans: "$join.answer3"
                }
            }
        }
    }
}, {
    $unwind: {
        path: '$answers',
        preserveNullAndEmptyArrays: true
    }
}, {
    $group: {
        _id: {
            _id: '$_id',
            Q1: '$answers.Q1.ans'
        },
        count: {
            $sum: 1
        }

    }
}
///...
}]

我不知道如何继续

【问题讨论】:

  • 每个问题你应该有 3 个可能的答案:好的、不好的、好的;这是一个现实的限制,还是您需要能够处理随机答案?
  • 必须处理随机答案

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

说明

步骤 1 - 3。你做得很好
第 4 步。我们需要使用 $objectToArray 运算符转换答案。

{answer1: "...", answer2: "...", answer3: "..."}
to 
[ {k:"answer1", v:"..."}, {k:"answer2", v:"..."}, {k:"answer3", v:"..."} ]

第 6 步。我们需要按 any answer (answer1, answer2, answer3) + any value (ok, bad, great, ...) 分组并计数 Nº 次
第 7 步。现在我们计算每个答案的总数
第 8 步。我们转换:

[{k:"ok|bad|great", v:"Nº times"},{k:"percentage", v:(Nº times/total) * 100}]
to
[{"ok|bad|great":1, percentage:100}]

第 9 步。现在我们添加到 ans -> answer1answer2answer3
第 10 步。我们调整所需的输出。


db.user.aggregate([
  {
    $lookup: {
      from: "answer",
      localField: "_id",
      foreignField: "userId",
      as: "join"
    }
  },
  {
    $unwind: "$join"
  },
  {
    $group: {
      _id: "$_id",
      mail: { $first: "$mail" },
      ans: {
        $push: {
          answer1: "$join.answer1",
          answer2: "$join.answer2",
          answer3: "$join.answer3"
        }
      }
    }
  },
  {
    $addFields: {
      ans: {
        $reduce: {
          input: {
            $map: {
              input: "$ans",
              in: { $objectToArray: "$$this" }
            }
          },
          initialValue: [],
          in: {
            $concatArrays: [
              "$$value",
              "$$this"
            ]
          }
        }
      }
    }
  },
  {
    $unwind: "$ans"
  },
  {
    $group: {
      _id: {
        userId: "$_id",
        mail: "$mail",
        k: "$ans.k",
        v: "$ans.v"
      },
      count: { $sum: 1 }
    }
  },
  {
    $group: {
      _id: {
        userId: "$_id.userId",
        mail: "$_id.mail",
        k: "$_id.k"
      },
      total: { $sum: "$count" },
      ans: {
        $push: {
          k: "$_id.k",
          v: "$_id.v",
          count: "$count"
        }
      }
    }
  },
  {
    $addFields: {
      ans: {
        $map: {
          input: "$ans",
          in: {
            $arrayToObject: [
              [
                {
                  k: "$$this.v",
                  v: "$$this.count"
                },
                {
                  k: "percentage",
                  v: {
                    $floor: {
                      $multiply: [
                        {
                          $divide: [
                            "$$this.count",
                            "$total"
                          ]
                        },
                        100
                      ]
                    }
                  }
                }
              ]
            ]
          }
        }
      }
    }
  },
  {
    $group: {
      _id: {
        userId: "$_id.userId",
        mail: "$_id.mail"
      },
      ans: {
        $push: {
          k: "$_id.k",
          v: "$ans"
        }
      }
    }
  },
  {
    $addFields: {
      _id: 1,
      mail: "$_id.mail",
      userId: "$_id.userId",
      ans: { $arrayToObject: "$ans" }
    }
  }
])

MongoPlayground

【讨论】:

  • 我正在使用spring boot,我想如果我编写mongo查询,我可以轻松转换为java。我昨天试图将其转换为弹簧靴形式,但失败了。我可以知道如何转换为弹簧靴!我急需这个。我在stackoverflow.com/questions/61381113/…中质疑过
【解决方案2】:

这绝对是可能的。这是一个蛮力尝试,可能有更优雅的方法来做到这一点。

这里的步骤是:

  • 查找以获取每个用户的答案
  • 解开答案
  • 将每个问题的答案汇总在一起,同时列出唯一答案
  • 映射每个唯一列表以计算它在另一个数组中出现的次数,使用格式{k: "answer", v: count},这样答案就可以成为以后的关键
  • 重新映射每个唯一列表以计算百分比,使用 array to object 转换为 {"answer":count} 并使用 mergeObjects 放置百分比
  • 删除临时字段的项目
db.user.aggregate([
  {$lookup: {
      from: "answer",
      localField: "_id",
      foreignField: "userId",
      as: "join"
  }},
  {$unwind: {
      path: "$join",
      preserveNullAndEmptyArrays: true
  }},
  {$group: {
      _id: "$_id",
      A1: { $push: "$join.answer1" },
      A2: { $push: "$join.answer2" },
      A3: { $push: "$join.answer3" },
      unique1: { $addToSet: "$join.answer1" },
      unique2: { $addToSet: "$join.answer2" },
      unique3: { $addToSet: "$join.answer3" }
   }},
  {$addFields: {
      answer1: {
        $map: {
          input: "$unique1",
          in: {
            k: "$$this",
            v: {
              $let: {
                vars: { ans: "$$this" },
                in: {
                  $size: {
                    $filter: {
                      input: "$A1",
                      cond: {
                        $eq: [
                          "$$ans",
                          "$$this"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      answer2: {
        $map: {
          input: "$unique2",
          in: {
            k: "$$this",
            v: {
              $let: {
                vars: {
                  ans: "$$this"
                },
                in: {
                  $size: {
                    $filter: {
                      input: "$A2",
                      cond: {
                        $eq: [
                          "$$ans",
                          "$$this"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      answer3: {
        $map: {
          input: "$unique3",
          in: {
            k: "$$this",
            v: {
              $let: {
                vars: {
                  ans: "$$this"
                },
                in: {
                  $size: {
                    $filter: {
                      input: "$A3",
                      cond: {
                        $eq: [
                          "$$ans",
                          "$$this"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
  }},
  {$addFields: {
      answer1: {
        $map: {
          input: "$answer1",
          in: {
            $mergeObjects: [
              {$arrayToObject: [[ "$$this" ]]},
              {percentage: {
                  $round: {
                    $multiply: [
                      100,
                      {$divide: [
                          "$$this.v",
                          {$size: "$A1"}
                      ]}
                    ]
                  }
              }}
            ]
          }
        }
      },
      answer2: {
        $map: {
          input: "$answer2",
          in: {
            $mergeObjects: [
              {$arrayToObject: [[ "$$this" ]]},
              {percentage: {
                  $round: {
                    $multiply: [
                      100,
                      {$divide: [
                          "$$this.v",
                          {$size: "$A2"}
                      ]}
                    ]
                  }
              }}
            ]
          }
        }
      },
      answer3: {
        $map: {
          input: "$answer3",
          in: {
            $mergeObjects: [
              {$arrayToObject: [[ "$$this" ]]},
              {percentage: {
                  $round: {
                    $multiply: [
                      100,
                      {$divide: [
                          "$$this.v",
                          {$size: "$A3"}
                      ]}
                    ]
                  }
              }}
            ]
          }
        }
      }
  }},
  {$project: {
      A1: 0,
      A2: 0,
      A3: 0,
      unique1: 0,
      unique2: 0,
      unique3: 0
   }}
])

Playground

【讨论】:

  • 非常感谢。它也有效。但我仅限于给出更正确的投票:D
【解决方案3】:

您的想法是正确的,我个人会从answers 集合开始聚合,因为在这种情况下$lookup 可能会变得非常昂贵。

db.answers.aggregate(
    [
        {
            $group: {
                _id: "$userIds",
                answer1: {$push: "$answer1"},
                answer2: {$push: "$answer2"},
                answer3: {$push: "$answer3"},
            }
        },
        {
            addFields: {
                answer1: {
                    $reduce: {
                        input: "$answer1",
                        initialValue: {great: 0, ok: 0, notgood: 0, bad: 0, total: 0},
                        in: {
                            total: {$add: ["$$value.total", 1]},
                            great: {$add: ["$$value.great", {$cond: [{$eq: ["$$this", "great"]}, 1, 0]}]},
                            ok: {$add: ["$$value.ok", {$cond: [{$eq: ["$$this", "ok"]}, 1, 0]}]},
                            notgood: {$add: ["$$value.notgood", {$cond: [{$eq: ["$$this", "Not good"]}, 1, 0]}]},
                            bad: {$add: ["$$value.bad", {$cond: [{$eq: ["$$this", "bad"]}, 1, 0]}]},
                        }
                    }
                },
                answer2: {
                    $reduce: {
                        input: "$answer2",
                        initialValue: {great: 0, ok: 0, notgood: 0, bad: 0, total: 0},
                        in: {
                            total: {$add: ["$$value.total", 1]},
                            great: {$add: ["$$value.great", {$cond: [{$eq: ["$$this", "great"]}, 1, 0]}]},
                            ok: {$add: ["$$value.ok", {$cond: [{$eq: ["$$this", "ok"]}, 1, 0]}]},
                            notgood: {$add: ["$$value.notgood", {$cond: [{$eq: ["$$this", "Not good"]}, 1, 0]}]},
                            bad: {$add: ["$$value.bad", {$cond: [{$eq: ["$$this", "bad"]}, 1, 0]}]},
                        }
                    }
                },
                answer3: {
                    $reduce: {
                        input: "$answer3",
                        initialValue: {great: 0, ok: 0, notgood: 0, bad: 0, total: 0},
                        in: {
                            total: {$add: ["$$value.total", 1]},
                            great: {$add: ["$$value.great", {$cond: [{$eq: ["$$this", "great"]}, 1, 0]}]},
                            ok: {$add: ["$$value.ok", {$cond: [{$eq: ["$$this", "ok"]}, 1, 0]}]},
                            notgood: {$add: ["$$value.notgood", {$cond: [{$eq: ["$$this", "Not good"]}, 1, 0]}]},
                            bad: {$add: ["$$value.bad", {$cond: [{$eq: ["$$this", "bad"]}, 1, 0]}]},
                        }
                    }
                },
            }
        },
        {
            $addFields: {
                ans: [
                    {
                        answer1: {
                            $arrayToObject: {
                                $filter: {
                                    input: {
                                        $map: {
                                            input: {$objectToArray: "$answer1"},
                                            as: "map_answer",
                                            in: {
                                                k: "$$map_answer.k", v: {$multiply: [{$divide: ["$$map_answer.v", "$answer1.total"]}, 100]}
                                            }
                                        }
                                    },
                                    as: "answer",
                                    cond: {
                                        $and: [
                                            {$ne: ["$$answer.k", "total"]},
                                            {$gt: ["$$answer.v", 0]}
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    {
                        answer2: {
                            $arrayToObject: {
                                $filter: {
                                    input: {
                                        $map: {
                                            input: {$objectToArray: "$answer2"},
                                            as: "map_answer",
                                            in: {
                                                k: "$$map_answer.k", v: {$multiply: [{$divide: ["$$map_answer.v", "$answer2.total"]}, 100]}
                                            }
                                        }
                                    },
                                    as: "answer",
                                    cond: {
                                        $and: [
                                            {$ne: ["$$answer.k", "total"]},
                                            {$gt: ["$$answer.v", 0]}
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    {
                        answer3: {
                            $arrayToObject: {
                                $filter: {
                                    input: {
                                        $map: {
                                            input: {$objectToArray: "$answer3"},
                                            as: "map_answer",
                                            in: {
                                                k: "$$map_answer.k", v: {$multiply: [{$divide: ["$$map_answer.v", "$answer3.total"]}, 100]}
                                            }
                                        }
                                    },
                                    as: "answer",
                                    cond: {
                                        $and: [
                                            {$ne: ["$$answer.k", "total"]},
                                            {$gt: ["$$answer.v", 0]}
                                        ]
                                    }
                                }
                            }
                        }
                    }

                ]
            }
        },
        {
            $lookup: {
                from: "users",
                localField: "userId",
                foreignField: "_id",
                as: "user"
            }
        },
        {
            $unwind: "$user"
        },
        {
            $project: {
                _id: "$user._id",
                userId: "$userId",
                mail: "$user.mail",
                ans: 1
            }
        }
    ]
)

正在进行大量数据操作,同时删除 0 使得管道需要额外的步骤。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多