【问题标题】:Order by date in sub-document and then by document在子文档中按日期排序,然后按文档排序
【发布时间】:2014-08-14 16:14:15
【问题描述】:

我有一个简单的“事件”mongo 模式。下面是两个示例文档:

事件文档#1

{
  "event_name": "Some nice event",
  "venues": [
    {
      "venue_name": "venue #1",
      "shows": [
        {
          "show_time": "2014-06-18T07:46:02.415Z",
          "capacity": 20
        },
        {
          "show_time": "2014-06-20T07:46:02.415Z",
          "capacity": 40
        }
      ]
    },
    {
      "venue_name": "venue #2",
      "shows": [
        {
          "show_time": "2014-06-17T07:46:02.415Z",
          "capacity": 20
        },
        {
          "show_time": "2014-06-24T07:46:02.415Z",
          "capacity": 40
        }
      ]
    }
  ]
}

事件文档#2

{
  "event_name": "Another nice event",
  "venues": [
    {
      "venue_name": "venue #1",
      "shows": [
        {
          "show_time": "2014-06-19T07:46:02.415Z",
          "capacity": 20
        },
        {
          "show_time": "2014-06-16T07:46:02.415Z",
          "capacity": 40
        }
      ]
    }
  ]
}

我需要查询此事件文档集合并获取与特定时间最接近的节目的事件。

因此,例如,如果我必须查找在 6 月 16 日或之后发生的事件,我应该获取文档 #2,然后是文档 #1,地点子文档顺序为 [地点 # 2,场地#1]。

另一方面,如果我希望在6 月 18 日 或之后发生事件,我应该获取文档 #1,其中包含 [地点 #1,地点 #2],然后是文档 #2。

基本上,我需要能够按嵌套子文档的 start_time 进行排序。而且这种排序应该适用于多个场地子文档。

根据mongo的文档,这个好像不支持,那么有没有办法使用聚合来实现呢?

或者有没有办法重新调整架构以支持此类查询?

或者 mongoDB 完全不适合这种场景?

【问题讨论】:

  • 这只是一个 JSON 转储吗?我希望您的实际“日期”字段是真正的“日期”对象而不是字符串。
  • 是的,当然。虽然我很想将它们存储为与 UTC 时间戳相对应的数字。
  • 我真的只是在问,因为您如何发布问题并不清楚。无论如何,我相信您已经得到了答案,那么您在这里的首要任务不应该是“谢谢”吗? “是的,当然”在这里确实没有任何价值。
  • 只是发帖谢谢,按cmets顺序通过!抱歉,如果这看起来不厚道。

标签: mongodb mongoose mongodb-query aggregation-framework


【解决方案1】:

非常好的问题。希望您的日期是真实日期,但词汇形式在这里并不重要。只要您考虑到日期,就应该使用以下表格:

db.event.aggregate([
    // Match the "documents" that meet the condition
    { "$match": {
        "venues.shows.show_time": { "$gte": new Date("2014-06-16") }
    }},

    // Unwind the arrays
    { "$unwind": "$venues" },
    { "$unwind": "$venues.shows" },

    // Sort the entries just to float the nearest result
    { "$sort": { "venues.shows.show_time": 1 } },

    // Find the "earliest" for the venue while grouping
    { "$group": {
        "_id": {
            "_id": "$_id",
            "event_name": "$event_name",
            "venue_name": "$venues.venue_name"
        },
        "shows": {
            "$push": "$venues.shows"
        },
        "earliest": { 
            "$min": {
                "$cond": [
                     { "$gte": [ 
                         "$venues.shows.show_time",
                         new Date("2014-06-16")
                     ]},
                     "$venues.shows.show_time",
                     null
                 ]
             }
        }
    }},

    // Sort those because of the order you want
    { "$sort": { "earliest": 1 } },

    // Group back and with the "earliest" document
    { "$group": {
        "_id": "$_id._id",
        "event_name": { "$first": "$_id.event_name" },
        "venues": {
           "$push": {
               "venue_name": "$_id.venue_name",
               "shows": "$shows"
           }
        },
        "earliest": { 
            "$min": {
                "$cond": [
                     { "$gte": [ 
                         "$earliest",
                         new Date("2014-06-16")
                     ]},
                     "$earliest",
                     null
                 ]
             }
        }
    }},

    // Sort by the earliest document
    { "$sort": { "earliest": 1 } },

    // Project the fields
    { "$project": {
        "event_name": 1,
        "venues": 1
    }}
])

因此,如果您对聚合框架有一定的经验,那么其中的大部分内容看起来都相当简单。如果不是,那么就会有一些一般性的解释,而且在我们进一步评估时还会发生一些“时髦”的事情。

聚合的第一步是$match,就像任何普通查询一样,然后是$unwind要处理的数组。 “unwind”语句有效地将数组中包含的文档“反规范化”为标准文档。

下一个$sort 以“美化”功能结束,因为每个“集合”中的“最早”事件将位于顶部。

由于有“两个”级别的数组,您可以通过$group 管道阶段在两个阶段中进行分组。

第一个$group“groups”按“document”、“event_name”和“venue”。所有节目都被放回原来的数组形式,但此时我们提取“show_time”的$min值。

所取的值不仅仅是普通的“最小”值。在这里,我们使用$cond 运算符来确保返回的值必须“大于或等于”您最初在查询中请求的日期。这样可以确保在“排序”时不考虑任何“较早”的值。

接下来要做的是在“最早”日期$sort,以保持“场地”条目的顺序。接下来的阶段则与上述相同,但这次将“分组”回原始文档,然后最终按“show_time”为“最早”的顺序“排序”。

显示为输入的日期的结果将是您想要的 16 日的结果:

{
    "_id" : ObjectId("53a95263a1923f45a6c2d3dd"),
    "event_name" : "Another nice event",
    "venues" : [
        {
            "venue_name" : "venue #1",
            "shows" : [
                {
                    "show_time" : ISODate("2014-06-16T07:46:02.415Z"),
                    "capacity" : 40
                },
                {
                    "show_time" : ISODate("2014-06-19T07:46:02.415Z"),
                    "capacity" : 20
                }
            ]
        }
    ]
}
{
    "_id" : ObjectId("53a952b5a1923f45a6c2d3de"),
    "event_name" : "Some nice event",
    "venues" : [
        {
            "venue_name" : "venue #2",
            "shows" : [
                {
                    "show_time" : ISODate("2014-06-17T07:46:02.415Z"),
                    "capacity" : 20
                },
                {
                    "show_time" : ISODate("2014-06-24T07:46:02.415Z"),
                    "capacity" : 40
                }
            ]
        },
        {
            "venue_name" : "venue #1",
            "shows" : [
                {
                    "show_time" : ISODate("2014-06-18T07:46:02.415Z"),
                    "capacity" : 20
                },
                {
                    "show_time" : ISODate("2014-06-20T07:46:02.415Z"),
                    "capacity" : 40
                }
            ]
        }
    ]
}

通过将输入更改为第 18 位,您还可以获得所需的结果:

{
    "_id" : ObjectId("53a952b5a1923f45a6c2d3de"),
    "event_name" : "Some nice event",
    "venues" : [
        {
            "venue_name" : "venue #1",
            "shows" : [
                {
                    "show_time" : ISODate("2014-06-18T07:46:02.415Z"),
                    "capacity" : 20
                },
                {
                    "show_time" : ISODate("2014-06-20T07:46:02.415Z"),
                    "capacity" : 40
                }
            ]
        },
        {
            "venue_name" : "venue #2",
            "shows" : [
                {
                    "show_time" : ISODate("2014-06-17T07:46:02.415Z"),
                    "capacity" : 20
                },
                {
                    "show_time" : ISODate("2014-06-24T07:46:02.415Z"),
                    "capacity" : 40
                }
            ]
        }
    ]
}
{
    "_id" : ObjectId("53a95263a1923f45a6c2d3dd"),
    "event_name" : "Another nice event",
    "venues" : [
        {
            "venue_name" : "venue #1",
            "shows" : [
                {
                    "show_time" : ISODate("2014-06-16T07:46:02.415Z"),
                    "capacity" : 40
                },
                {
                    "show_time" : ISODate("2014-06-19T07:46:02.415Z"),
                    "capacity" : 20
                }
            ]
        }
    ]
}

此外,如果您想更进一步,只需添加一个额外的$match 阶段,它可以过滤掉查询中请求的日期之前发生的“事件”:

db.event.aggregate([
    { "$match": {
        "venues.shows.show_time": { "$gte": new Date("2014-06-18") }
    }},
    { "$unwind": "$venues" },
    { "$unwind": "$venues.shows" },
    { "$match": {
        "venues.shows.show_time": { "$gte": new Date("2014-06-18") }
    }},
    { "$sort": { "venues.shows.show_time": 1 } },
    { "$group": {
        "_id": {
            "_id": "$_id",
            "event_name": "$event_name",
            "venue_name": "$venues.venue_name"
        },
        "shows": {
            "$push": "$venues.shows"
        },
        "earliest": { 
            "$min": {
                "$cond": [
                     { "$gte": [ 
                         "$venues.shows.show_time",
                         new Date("2014-06-18")
                     ]},
                     "$venues.shows.show_time",
                     null
                 ]
             }
        }
    }},
    { "$sort": { "earliest": 1 } },
    { "$group": {
        "_id": "$_id._id",
        "event_name": { "$first": "$_id.event_name" },
        "venues": {
           "$push": {
               "venue_name": "$_id.venue_name",
               "shows": "$shows"
           }
        },
        "earliest": { 
            "$min": {
                "$cond": [
                     { "$gte": [ 
                         "$earliest",
                         new Date("2014-06-18")
                     ]},
                     "$earliest",
                     null
                 ]
             }
        }
    }},
    { "$sort": { "earliest": 1 } },
    { "$project": {
        "event_name": 1,
        "venues": 1
    }}
])

结果:

{
    "_id" : ObjectId("53a952b5a1923f45a6c2d3de"),
    "event_name" : "Some nice event",
    "venues" : [
        {
            "venue_name" : "venue #1",
            "shows" : [
                {
                    "show_time" : ISODate("2014-06-18T07:46:02.415Z"),
                    "capacity" : 20
                },
                {
                    "show_time" : ISODate("2014-06-20T07:46:02.415Z"),
                    "capacity" : 40
                }
            ]
        },
        {
            "venue_name" : "venue #2",
            "shows" : [
                {
                    "show_time" : ISODate("2014-06-24T07:46:02.415Z"),
                    "capacity" : 40
                }
            ]
        }
    ]
}
{
    "_id" : ObjectId("53a95263a1923f45a6c2d3dd"),
    "event_name" : "Another nice event",
    "venues" : [
        {
            "venue_name" : "venue #1",
            "shows" : [
                {
                    "show_time" : ISODate("2014-06-19T07:46:02.415Z"),
                    "capacity" : 20
                }
            ]
        }
    ]
}

【讨论】:

  • 这太棒了。我尝试使用聚合框架,但 $sort 是我卡住的地方。 $cond 似乎可以解决问题,感谢您指出这一点!最后一个后续问题 - 使用这种聚合框架是否存在严重的性能损失?再次感谢这个有见地的回答!
  • @Neeharv 最初使用$match 通常应该超过主要的“惩罚”,其目的是“限制”作为文档进入聚合管道的输入。在那之后,将$unwind 与“大”数组一起使用不会有任何好处。请参阅$map 操作员和/或浏览我从my profile 发布的各种问答式问题,以更好地了解如何使用它。当然是 MongoDB 2.6 或更高版本。
猜你喜欢
  • 1970-01-01
  • 2015-11-25
  • 2016-04-10
  • 1970-01-01
  • 1970-01-01
  • 2014-06-23
  • 2016-10-07
  • 2013-10-21
  • 1970-01-01
相关资源
最近更新 更多