【问题标题】:Aggregate nested array in MongodbMongodb中的聚合嵌套数组
【发布时间】:2015-12-06 17:37:00
【问题描述】:

我有一个 mongo 集合,谎言如下:

    {
   "_id":ObjectId("55f16650e3cf2242a79656d1"),
   "user_id":11,
   "push":[
      ISODate("2015-09-08T11:14:18.285      Z"),
      ISODate("2015-09-08T11:14:18.285      Z"),
      ISODate("2015-09-09T11:14:18.285      Z"),
      ISODate("2015-09-10T11:14:18.285      Z"),
      ISODate("2015-09-10T11:14:18.285      Z")
   ]
}{
   "_id":ObjectId("55f15c78e3cf2242a79656c3"),
   "user_id":12,
   "push":[
      ISODate("2015-09-06T11:14:18.285      Z"),
      ISODate("2015-09-05T11:14:18.285      Z"),
      ISODate("2015-09-07T11:14:18.285      Z"),
      ISODate("2015-09-09T11:14:18.285      Z"),
      ISODate("2015-09-09T11:14:18.285      Z"),
      ISODate("2015-09-10T11:14:18.285      Z"),
      ISODate("2015-09-11T11:14:18.285      Z")
   ]
}

如何在单个查询中找到 timeStamps (currentDate-5) 的 user_ids。我将使用 php 并且不想将所有文档都放在内存中。

解释:

user_id : date       : count
11      : 2015-09-08 : 2
          2015-09-09 : 1
          2015-09-10 : 2

12      : 2015-09-05 : 1
          2015-09-06 : 1
          2015-09-07 : 1
          2015-09-09 : 2
          2015-09-10 : 1
          2015-09-11 : 1

如果日期设置为 2015-09-09(用户输入),它将为 user_id 11 提供 3(计数),为 user_id 12 提供 4(计数)。因此假设计数设置为 3(用户输入)。查询应返回 11(user_id)。如果 count 设置为 2,将没有可用的 user_id,如果 count 设置为 5,它应该返回 11 和 12

【问题讨论】:

  • 哎哟!为什么将字段命名为“推”?这几乎与您可以得到的故意造成混淆的代码一样接近。似乎是某人的“错字”。但是您真的是要查找“少于三个”的数组项的“计数”吗?
  • 推送我的意思是push_notification,有很多像sms这样的字段,现在我猜它理解了,这些是发送推送时的时间戳。并且是那些大于特定日期的数组项的计数
  • 我的意思是,“结果代码”通常看起来像 { "$push": { "push": "something" } }(作为 JSON 表示),无论如何让读者感到非常困惑。清晰的字段命名将有助于代码的可读性。这里的另一点是“什么日期”?您的问题完全没有这一点,当我认为您不清楚时,为什么 在 cmets 中询问。请编辑告诉我们您未提及的“日期标准”。除非您的意思是文档的基本选择,或者您只想“计算”在该时间范围内“发生”的数组条目?
  • 我已经更新了问题,但之前我写了“过去 5 天内 numberOfTimeStamps 小于 3”所以“5 天”描述了日期标准
  • 好吧。这至少说明了这一点。请理解它是/在问题中呈现的方式是/是开放的解释。因此可能导致错误的结果/响应。为什么我问。

标签: php mongodb mongodb-query aggregation-framework


【解决方案1】:

要解决这个问题,您需要一个聚合管道,它首先将结果“过滤”到“过去 5 天”,然后基本上“汇总”每个合格文档中存在的数组项的计数,然后查看“总数”是否为“少于三个”。

MongoDB 聚合的 $size 运算符在这里确实有帮助,$map 以及通过 $setDifference 对从 false 返回的结果进行一些额外的过滤,从 $map 返回,就像“在文档中优先”和“在“所需的$group 阶段内,是处理此问题的最有效方法

$result = $collection->aggregate(array(
    array( '$match' => array(
        'push' => array( 
            'time' => array( 
                '$gte' =>  MongoDate( strtotime('-5 days',time()) )
            )
        )     
    )),
    array( '$group' => array(
        '_id' => '$user_id',
        'count' => array(
            '$sum' => array(
                '$size' => array(
                    '$setDifference' => array(
                        array( '$map' => array(
                            'input' => '$push',
                            'as' => 'time',
                            'in' => array(
                                '$cond' => array(
                                    array( '$gte' => array(
                                        '$$time',
                                        MongoDate( strtotime('-5 days',time()) )
                                    )),
                                    '$time',
                                    FALSE
                                )
                            ) 
                        )),
                        array(FALSE)
                    )
                )
            )
        )
    )),
    array( '$match' => array(
        'count' => array( '$lt' => 3 )
    )) 
));

所以完成所有工作后,首先通过$match找到包含满足条件的数组条目的“可能”文档,然后在$group下找到匹配数组项的“总”大小,然后是最终$match 排除总大小小于三个的所有结果。


对于大部分的“JavaScript 大脑”(比如我自己,训练有素),这基本上就是这个结构:

db.collection.aggregate([
    { "$match": {
        "push": {
            "$gte": new Date( new Date().valueOf() - ( 5 * 1000 * 60 * 60 * 24 ))
        }
    }},
    { "$group": {
        "_id": "$user_id",
        "count": {
            "$sum": {
                "$size": {
                    "$setDifference": [
                        { "$map": {
                            "input": "$push",
                            "as": "time",
                            "in": {
                                "$cond": [
                                    { "$gte": [ 
                                        "$$time",
                                        new Date( 
                                            new Date().valueOf() - 
                                            ( 5 * 1000 * 60 * 60 * 24 )
                                        )
                                    ]},
                                    "$$time",
                                    false
                                ]
                            }
                        }},
                        [false]
                    ]
                }
            }
        }
    }},
    { "$match": { "count": { "$lt": 3 } } }
])

另外,MongoDB 的未来版本将提供$filter,它简化了整个$map$setDifference 语句部分:

db.collection.aggregate([
    { "$match": {
        "push": {
            "$gte": new Date( new Date().valueOf() - ( 5 * 1000 * 60 * 60 * 24 ))
        }
    }},
    { "$group": {
        "_id": "$user_id",
        "count": {
            "$sum": {
                "$size": {
                    "$filter": {
                        "input": "$push",
                        "as": "time",
                        "cond": {
                            "$gte": [
                                "$$time",
                                new Date( 
                                    new Date().valueOf() - 
                                    ( 5 * 1000 * 60 * 60 * 24 )
                                )                       
                            ]
                        }
                    }
                }
            }
        }
    }},
    { "$match": { "count": { "$lt": 3 } } }
])

同时注意“日期”可能最好在管道定义“之前”计算为一个单独的变量以获得最佳准确性。

【讨论】:

  • 先生,得到错误结果:docs.google.com/document/d/…
  • @lalit 回头看看前面一长串的 cmets,你明确说 “5 天内时间戳的数量少于 3 个”,这正是通过“过滤”数组中的时间戳以返回 5 天内发生的时间戳,这里发生了什么。您不仅在此“测试”中更改标准,而且在我至少两次询问您以澄清这不是之后,您说您“期望”来自数组的“完整未过滤”结果你的意思是。每次“我已经说过了......(重复)..”。你得到了你想要的。 “谢谢”是回应。
  • 我想我应该写“timeStamps (currentDate-5)”计数我的意思是解释部分中的列名。对困惑感到抱歉。将尝试相应地编辑答案和问题。非常感谢
  • @lalit Umm。如果那应该是 SQL,那么上面就是这样做的。正如已经解释的那样。时间戳被“过滤”为仅比当前时间早 5 天的时间戳,然后仅计算那些匹配的时间戳,我们仅返回(拥有)那些计数大于 3 的时间戳。正是翻译。你只是逻辑错了。
猜你喜欢
  • 2017-07-26
  • 1970-01-01
  • 2017-07-16
  • 1970-01-01
  • 2021-06-27
  • 2020-01-25
  • 2021-03-19
  • 1970-01-01
  • 2023-01-13
相关资源
最近更新 更多