【问题标题】:Find total unique visitors based on unique id - MongoDB根据唯一 ID 查找唯一身份访问者总数 - MongoDB
【发布时间】:2022-02-05 01:21:41
【问题描述】:

根据此数据中的唯一身份访问者 ID 查找唯一身份访问者的最佳方法是什么? 我在 Nodejs/Express 环境中使用 Mongoose。

{
        username: "jack",
        events: [
         {
           eventType: "party",
           createdAt: "2022-01-23T10:26:11.214Z",
           visitorInfo: {
                visitorId: "87654321-0ebb-4238-8bf7-87654321"
               }
         },
         {
           eventType: "party",
           createdAt: "2022-01-30T10:26:11.214Z",
           visitorInfo: {
                visitorId: "87654321-0ebb-4238-8bf7-87654321"
               }
         },
         {
           eventType: "party",
           createdAt: "2022-01-29T10:26:11.214Z",
           visitorInfo: {
                visitorId: "12345678-0ebb-4238-8bf7-12345678"
               }
         },
        {
           eventType: "party",
           createdAt: "2022-01-31T10:16:11.214Z",
           visitorInfo: {
                visitorId: "12345678-0ebb-4238-8bf7-12345678"
               }
         }
           ]

        }

我正在尝试什么:

    Event.aggregate([
          { $match: "jack" },
          { $unwind: "$events" },
          { $match: { "events.eventType": "party" } },
    
          {
            $group: {
              _id: "$events.visitorInfo.visitorId",
              count: { $sum: 1 },
            },
          },
        ]);

这给出了唯一访问者 ID 的频率,我认为可能有一种方法可以使用它来获取唯一访问者,但我正在尝试找到一种更快、更快的方法来根据唯一访问者 ID 查找唯一访问者数量,然后再去进入兔子洞。

我最后的期待:

    visitors: {
        totalVisitors: 4,
        uniqueVisitors: 2
       }

感谢任何帮助或指导。谢谢!

【问题讨论】:

    标签: javascript mongodb mongoose nosql aggregate


    【解决方案1】:

    一种方法是使用maplodash

    1. 首先使用简单的find({}) 获取所有文档。
    2. 在结果上使用地图。
    3. 使用 lodash。

    const vistorsIdsList = result.map(_ => visitorInfo.visitorId)
    
    const vistors = {
      total: vistorsIdsList.length,
      uniq: lodash.uniq(vistorsIdsList).length
    }

    lodash

    【讨论】:

    • hmm 这只会给我 uniqueIds 的数量,而不是基于 uniqueIds 的事件数量,对吧?此外,不确定将 map/lodash 与 mongoose 查询混合是否是一个好主意,尤其是出于性能原因。感谢您的意见!
    【解决方案2】:

    我认为你实际上是在正确的轨道上。您只需要在$unwind 之前执行$reduce 即可保留totalVisitors。在最后阶段,再执行一次$group 以获得您预期的结果。

    db.event.aggregate([
      {
        "$match": {
          username: "jack"
        }
      },
      {
        "$addFields": {
          "totalVisitors": {
            "$reduce": {
              "input": "$events",
              "initialValue": 0,
              "in": {
                "$cond": {
                  "if": {
                    $eq: [
                      "$$this.eventType",
                      "party"
                    ]
                  },
                  "then": {
                    $add: [
                      "$$value",
                      1
                    ]
                  },
                  "else": "$$value"
                }
              }
            }
          }
        }
      },
      {
        $unwind: "$events"
      },
      {
        $match: {
          "events.eventType": "party"
        }
      },
      {
        $group: {
          _id: "$events.visitorInfo.visitorId",
          count: {
            $sum: 1
          },
          totalVisitors: {
            $first: "$totalVisitors"
          }
        }
      },
      {
        $group: {
          _id: null,
          count: {
            $first: "$count"
          },
          totalVisitors: {
            $first: "$totalVisitors"
          }
        }
      }
    ])
    

    这里是Mongo Playground 供您参考。

    【讨论】:

    • 谢谢@ray。它似乎给出了正确的输出,我只是想知道查询是否是最高效的,因为我期望事件数组中有成千上万个对象。
    • @lisa 首先,由于16MB limit,我不确定数组是否可以容纳如此大量的条目。如果您担心性能,您可能需要正确索引您的集合。对于您的示例,username 上的索引将有助于 $matchevents.visitorInfo.visitorId 上的索引将有助于 $group
    【解决方案3】:

    版本 1(过滤器):

    恕我直言,这似乎是最好的版本,为了获得最佳效果,您需要“用户名”上的索引

    db.collection.aggregate([
    {
     $match: {
      username: "jack"
    }
    },
    {
    $project: {
      p: {
        $filter: {
          input: "$events",
          as: "item",
          cond: {
            $eq: [
              "$$item.eventType",
              "party"
            ]
           }
         }
       }
     }
     },
     {
      $unwind: "$p"
     },
     {
      $group: {
      _id: "0",
      visitors: {
        $push: "$p.visitorInfo.visitorId"
      },
      uniqueVisitors: {
        $addToSet: "$p.visitorInfo.visitorId"
       }
      }
     },
     {
      $project: {
      _id: 0,
      visitorsCount: {
        $size: "$visitors"
      },
      uniqueVisitorsCount: {
        $size: "$uniqueVisitors"
       }
      }
     }
    ])
    

    解释:

    1. $match -> 只匹配带有 username:jack 的文档
    2. $project -> $filter only events with evetType: party
    3. $unwind 过滤后的数组以适合小组赛阶段
    4. $group -> 访客:[all] , uniqueVisitors:[unique]
    5. $project: visitorCount , uniqueVisitorCount

    play

    版本 2(不使用过滤器 - 适用于 atlas free 轮胎)

       db.collection.aggregate([
      {
       $match: {
       username: "jack"
       }
       },
       {
        $unwind: "$events"
       },
       {
        $match: {
           "events.eventType": "party"
        }
       },
       {
        $group: {
        _id: "0",
         visitors: {
            $push: "$events.visitorInfo.visitorId"
         },
         uniqueVisitors: {
            $addToSet: "$events.visitorInfo.visitorId"
         }
        }
        },
        {
          $project: {
            _id: 0,
           visitorsCount: {
             $size: "$visitors"
            },
            uniqueVisitorsCount: {
             $size: "$uniqueVisitors"
            }
           }
          }
          ])
    
    1. $match -> 只匹配带有 username:jack 的文档
    2. 解开 $events,因为 $filter 在免费轮胎中是不可能的
    3. $match the eventType:party
    4. $group -> 访客:[all] , uniqueVisitors:[unique]
    5. $project: visitorCount , uniqueVisitorCount

    play ( version without filter suitable for the atlas free tire )

    【讨论】:

    • 谢谢@R2D2。我正在使用 Atlas 的免费层,不幸的是无法使用过滤器。但我会记住这个方法。
    • $filter 是在 mongodb 版本 >=3.2 中引入的,我猜 Atlas 没那么老了 :)
    • 在我的答案中添加了一个修改选项,没有更适合免费轮胎的过滤器(我发现 atlas 故意不允许在免费轮胎中使用过滤器)
    【解决方案4】:

    第 3 版:有趣的第 3 个选项仅使用项目/过滤器/减少(无组和展开)

     db.collection.aggregate([
     {
       $match: {
         username: "jack"
     }
     },
     {
       $project: {
        p: {
         $filter: {
          input: "$events",
          as: "item",
          cond: {
            $eq: [
              "$$item.eventType",
              "party"
            ]
           }
          }
         }
        }
      },
      {
       $project: {
        total: {
          $reduce: {
           input: "$p",
            initialValue: {
              visitor: [],
             uniquevisitor: []
            },
          in: {
            visitor: {
              $concatArrays: [
                "$$value.visitor",
                [
                  "$$this.visitorInfo.visitorId"
                ]
              ]
            },
            uniquevisitor: {
              $cond: [
                {
                  $in: [
                    "$$this.visitorInfo.visitorId",
                    "$$value.uniquevisitor"
                  ]
                },
                "$$value.uniquevisitor",
                {
                  $concatArrays: [
                    "$$value.uniquevisitor",
                    [
                      "$$this.visitorInfo.visitorId"
                    ]
                  ]
                }
               ]
              }
             }
           }
          }
        }
       },
      {
       $project: {
      _id: 0,
      visitorsCount: {
        $size: "$total.visitor"
      },
      uniqueVisitorsCount: {
         $size: "$total.uniquevisitor"
        }
       }
      }
     ])
    

    解释:

    1. $match the docs with username: jack
    2. $filter 带有 events.eventType:party 的文档
    3. $reduce/$cond/$in 并形成两个具有唯一和所有事件的数组。
    4. $project 计算唯一性和所有性。

    play

    【讨论】:

      【解决方案5】:

      第 4 版。仅使用 $filter/$project 优化(无 unwind,group) (灵感来自@turvishal)

       db.collection.aggregate([
       {
       $match: {
        username: "jack"
       }
       },
       {
        $project: {
        events: {
          $filter: {
            input: "$events",
            cond: {
              $eq: [
                "$$this.eventType",
                "party"
              ]
             }
            }
           }
         }
       },
       {
        $project: {
        _id: 0,
        visitor: {
          $size: "$events.visitorInfo.visitorId"
         },
         uniquevisitor: {
          $size: {
            $setUnion: "$events.visitorInfo.visitorId"
          }
         }
        }
       }
      ])
      

      解释:

      1. $match 只匹配用户名:jack 的文档
      2. $project/$filter only eventType:"party"
      3. $project 访问者和独立访问者 ($setUnion)

      play

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-04-29
        • 2016-08-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-29
        • 1970-01-01
        相关资源
        最近更新 更多