【问题标题】:Sorting by maximum array field, ascending or descending按最大数组字段排序,升序或降序
【发布时间】:2016-02-14 14:35:42
【问题描述】:

在我的 Meteor 应用程序中,我有一组文档,其中包含一组子文档,如下所示:

/* 1 */
{
    "_id" : "5xF9iDTj3reLDKNHh",
    "name" : "Lorem ipsum",
    "revisions" : [ 
        {
            "number" : 0,
            "comment" : "Dolor sit amet",
            "created" : ISODate("2016-02-11T01:22:45.588Z")
        }
    ],
    "number" : 1
}

/* 2 */
{
    "_id" : "qTF8kEphNoB3eTNRA",
    "name" : "Consecitur quinam",
    "revisions" : [ 
        {
             "comment" : "Hoste ad poderiquem",
             "number" : 1,
             "created" : ISODate("2016-02-11T23:25:46.033Z")
        }, 
        {
            "number" : 0,
            "comment" : "Fagor questibilus",
            "created" : ISODate("2016-02-11T01:22:45.588Z")
        }
    ],
    "number" : 2
}

我想要做的是查询这个集合并按照revisions 数组的created 字段中的最大日期 对结果集进行排序。一些我还没有完成的事情。我有一些限制:

  • 仅按revisions.created 排序并不能减少它,因为从集合中使用的日期取决于排序方向。无论排序顺序如何,我都必须使用集合中的最大日期
  • 我不能依赖对未排序结果集的查询后操作,因此,这必须通过数据库的适当查询或聚合来完成。
  • 无法保证revisions 数组会被预先排序。
  • 在某些文档中可能有额外的字段,这些字段必须附带,所以请小心$project
  • Meteor 仍在使用 MongoDB 2.6,较新的 API 功能不好:(

【问题讨论】:

  • 在 shell 上,应该是 db.coll.find(yourQuery).sort({"revisions.created":-1})。我对流星一无所知,所以你需要自己翻译。
  • @Markus 当我按降序而不是升序排序时有效。然后它使用集合中的最小值。
  • 嗯,泰国人正是你的问题所说的。您可能想改写它并更详细地描述问题。
  • @Markus 没错。立即编辑。
  • 我还是不明白。您不会按特定日期排序。您按日期排序,升序或降序。是否有可能要查询共享最大日期的所有记录?

标签: mongodb meteor mongodb-query aggregation-framework


【解决方案1】:

您在这里所问的基本问题归结为有问题的数据位于“数组”内,因此 MongoDB 对如何处理有一些基本假设。

如果您按“降序”应用排序,那么 MongoDB 将完全按照您的要求进行,并按数组中指定字段的“最大”值对文档进行排序:

.sort({ "revisions.created": -1 ))

但是,如果您改为按“升序”排序,那么当然反过来也是如此,并且会考虑“最小”值。

.sort({ "revisions.created": 1 })

因此,这样做的唯一方法是从数组中的数据中找出最大日期,然后对该结果进行排序。这基本上意味着应用.aggregate(),对于meteor来说这是一个服务器端操作,不幸的是这样的:

Collection.aggregate([
    { "$unwind": "$revisions" },
    { "$group": {
        "_id": "$_id",
        "name": { "$first": "$name" },
        "revisions": { "$push": "$revisions" },
        "number": { "$first": "$number" }
        "maxDate": { "$max": "$revisions.created" }
    }},
    { "$sort": { "maxDate": 1 }
])

或者充其量使用 MongoDB 3.2,其中 $max 可以直接应用于数组表达式:

Collection.aggregate([
    { "$project": {
        "name": 1,
        "revisions": 1,
        "number": 1,
        "maxDate": {
            "$max": {
                "$map": {
                    "input": "$revisions",
                    "as": "el",
                    "in": "$$el.created"
                }
            }
        }
    }},
    { "$sort": { "maxDate": 1 } }
])

但实际上两者都不是那么好,即使 MongoDB 3.2 方法的开销比以前版本可用的方法少得多,但由于需要通过数据并计算出要排序的值。

因此,为了最佳性能,“始终”将您需要的此类数据保留在数组“外部”。为此,有$max "update" 运算符,它只会在“如果”提供的值“大于”现有值“大于”文档中的值时替换文档中的值。即:

Collection.update(
    { "_id": "qTF8kEphNoB3eTNRA" },
    { 
        "$push": {
            "revisions": { "created": new Date("2016-02-01") }            
        },
        "$max": { "maxDate": new Date("2016-02-01") }
    }
)

这意味着您想要的值将“始终”以预期值存在于文档中,因此现在只需对该字段进行排序即可:

.sort({ "maxDate": 1 })

因此,为了我的钱,我会使用可用的.aggregate() 语句来检查现有数据,并使用这些结果更新每个文档以包含“maxDate”字段。然后更改数组数据的所有添加和修订的编码,以在每次更改时应用 $max "update"

如果您经常使用它,那么拥有一个实心字段而不是计算总是更有意义。而且维护也很简单。


在任何情况下,考虑到上面应用的示例日期,它“小于”其他存在的最大日期会以所有形式返回给我:

{
        "_id" : "5xF9iDTj3reLDKNHh",
        "name" : "Lorem ipsum",
        "revisions" : [
                {
                        "number" : 0,
                        "comment" : "Dolor sit amet",
                        "created" : ISODate("2016-02-11T01:22:45.588Z")
                }
        ],
        "number" : 1,
        "maxDate" : ISODate("2016-02-11T01:22:45.588Z")
}
{
        "_id" : "qTF8kEphNoB3eTNRA",
        "name" : "Consecitur quinam",
        "revisions" : [
                {
                        "comment" : "Hoste ad poderiquem",
                        "number" : 1,
                        "created" : ISODate("2016-02-11T23:25:46.033Z")
                },
                {
                        "number" : 0,
                        "comment" : "Fagor questibilus",
                        "created" : ISODate("2016-02-11T01:22:45.588Z")
                },
                {
                        "created" : ISODate("2016-02-01T00:00:00Z")
                }
        ],
        "number" : 2,
        "maxDate" : ISODate("2016-02-11T23:25:46.033Z")
}

考虑到“maxDate”,正确地将第一个文档放在排序顺序的顶部。

【讨论】:

  • 感谢您的详细回答。我遇到的一个问题是,每次添加新修订时,都必须记住更新maxDate 字段,否则它将中断。如果有办法实现自动化,那也是可以接受的。
  • @edgerunner 这基本上就是我在这里所说的。因此,无论是现有元素上的$push 还是$set(实际上只有在“创建”实际在那里更改时),那么您也将应用$max。到文档的maxDate 值。是的,这是更多的工作。如果需要,您可以添加一些代码以将该“密钥”添加到任何更新对象,但要小心在同一 maxDate 路径上没有其他操作(不是您应该为此目的)。这里的权衡是对每次更新都这样做,或者在每个请求上交替使用聚合。
猜你喜欢
  • 2017-06-10
  • 2019-07-17
  • 2018-03-18
  • 2021-03-27
  • 1970-01-01
  • 1970-01-01
  • 2015-08-30
  • 2017-03-21
  • 2015-11-30
相关资源
最近更新 更多