Mongo $subtract date 在聚合 $match 块中不起作用

Mongo $subtract date doesn't work in aggregation $match block

我正在创建一个 mongo 聚合查询,它在我的 $match 块中使用 $subtract 运算符。正如下面这些代码中所解释的。

此查询无效:

db.coll.aggregate(
[
    {
        $match: {
            timestamp: {
                $gte: {
                    $subtract: [new Date(), 24 * 60 * 60 * 1000]
                }
            }
        }
    },
    {
        $group: {
            _id: {
                timestamp: "$timestamp"
            },
            total: {
                $sum: 1
            }
        }
    },
    {
        $project: {
            _id: 0,
            timestamp: "$_id.timestamp",
            total: "$total",
        }
    },
    {
        $sort: {
            timestamp: -1
        }
    }
]
)

但是,第二个查询有效:

db.coll.aggregate(
[
    {
        $match: {
            timestamp: {
                $gte: new Date(new Date() - 24 * 60 * 60 * 1000)
            }
        }
    },
    {
        $group: {
            _id: {
                timestamp: "$timestamp"
            },
            total: {
                $sum: 1
            }
        }
    },
    {
        $project: {
            _id: 0,
            timestamp: "$_id.timestamp",
            total: "$total",
        }
    },
    {
        $sort: {
            timestamp: -1
        }
    }
]
)

我需要在我的 $match 块上使用 $subtract,所以我不能使用最后一个查询。

$subtract 运算符是投影运算符。它仅在 $project 步骤期间可用。所以你的选择是:

  • (不推荐)在您的$match-step 之前添加一个$project-step 以转换所有文档的timestamp 字段以用于后续的match-step。我不建议您这样做,因为此操作需要对数据库中的每个文档执行,并且会阻止数据库在时间戳字段上使用索引,因此可能会消耗大量性能。
  • (推荐)在应用程序的 shell / 中生成要匹配的日期。生成一个新的 Date() 对象,将其存储在一个变量中,从中减去 24 小时并使用该变量执行第二次查询。

好吧,你不能那样做,你也不打算那样做。另一件有效的事情是你对 "need" 说要这样做,但实际上你真的没有这样做。

几乎所有一般 aggregation operators outside of the pipeline operators are really only valid within a $project or a $group 流水线阶段。主要在 $project 内,但肯定不在其他人内。

一个$match pipeline is really the same as a general "query" operation, so the only things valid in there are the query operators.

至于您的 "need" 的情况,在聚合管道内提交的任何 "value",特别是在 $match 内提交的任何 "value" 都需要在实际管道之外进行评估,然后再BSON表示被发送到服务器。

唯一的例外是文档中定义变量的符号,特别是 "fieldnames" 这样的 "$fieldname",然后才真正出现在 $project$group 中。所以这意味着 "refers" 到文档的现有值,这是在任何类型的 "query" 文档表达式中无法完成的事情。

如果您需要使用文档中另一个字段的值,那么您首先使用 $project 进行计算,如:

db.collection.aggregate([
    { "$project": {
        "fieldMath": { "$subtract": [ "$fieldOne", "$fieldTwo" ] }
    }},
    { "$match": { "fieldMath": { "$gt": 2 } }}
])

出于任何其他目的,您确实想要评估管道的价值 "outside"。


以上回答了您提出的问题,但这回答了您没有提出的问题。

您的管道没有任何意义,因为单独对 "timestamp" 进行分组不太可能对任何内容进行分组,因为这些值的精度为毫秒,而且最多可能不会超过几个对于非常活跃的系统。

您似乎正在寻找要按 "day" 分组的数学运算,您可以这样做:

db.collection.aggregate([
    { "$group": {
        "_id": {
            "$subtract": [
                { "$subtract": [ "$timestamp", new Date(0) ] },
                { "$mod": [
                    { "$subtract": [ "$timestamp", new Date(0) ] },
                    1000 * 60 * 60 * 24
                ]}
            ]
        },
        "total": { "$sum": "$total" }
    }}
])

那 "rounds" 你的时间戳值到一天,并且有更好的机会 "aggregating" 比你本来拥有的东西。

或者您可以使用 "date aggregation operators" 与复合键做同样的事情。


所以如果你想 "query" 那么它会在外部进行评估。如果您想处理一个值 "within the document",那么您必须在 $project$group 流水线阶段进行。

从 mongodb 3.6 开始,您可以通过 $expr 在 $match 阶段使用 $subtract。这是文档:https://docs.mongodb.com/manual/reference/operator/query/expr/

我能够通过此 $expr 和 mongodb 4.2 中名为 $$NOW 的新系统变量获得您所描述的查询。这是我的查询,它为我提供了过去 4 小时内创建的订单:

[ 
 { $match: 
  { $expr: 
   { $gt: [ 
    "$_created_at", 
     { $subtract: [ "$$NOW", 4 * 60 * 60 * 1000] } ] 
   } 
  } 
 }
]