【问题标题】:MongoDB Grouping QueryMongoDB分组查询
【发布时间】:2015-10-09 20:31:16
【问题描述】:

我正在开发 Meteor 应用程序,并且我有每周时间表格式的数据 -

events:
    event:
       day: "Monday"
       time: "9am"
       location: "A"
    event:
       day: "Monday"
       time: "10am"
       location: "B"

每天都有很多条目。我可以运行一个返回格式对象的查询吗 -

day: "Monday"
   events:
      event:
         time: "9am"
         location: "A"
      event:
         time: "10am"
         location: "B"

我可以以第二种格式存储对象,但更喜欢第一种格式,以便于删除和更新单个事件。

如果有好的方法,我也想按星期几退货。

【问题讨论】:

  • 您能否向我们展示代表上述数据的确切 MongoDB 文档架构?
  • event = { "_id" : int, "day" : string, "time" : string, "location" : string }
  • 我将有一个用于添加、删除或修改新事件的表单,但是当我显示它们时,它们将按天分组。我知道我可以在前端完成这一切,但在查询中可能会更整洁。第一次这样做可能会出错。
  • @kinsey 所以您需要在发布时对这些数据进行排序和分组,还是仅用于显示目的? IE。当您需要重塑事件文档时,它们是否已经在客户端上?
  • 仅用于显示目的 - 我现在看到这一天实际上是存储为 int 而不是 String 所以排序很容易。日期字符串是从带有数字值的选择框中选择的。

标签: mongodb meteor


【解决方案1】:

几个选项:

  1. 您可以使用聚合命令,但请注意,您会失去反应性:这意味着除非您重新加载模板,否则您不会获得外部更新。您还需要使用a package to add the aggregation command to Meteor 来实现这一点。

  2. 我个人的最爱:您无需聚合(和松散的反应性)即可实现数据转换。您可以使用一个简单的Collection.find() 查询并使用cursor.Observe() 和条件修改的巧妙组合来扩展/减少/修改它。看看这个答案,它对我有用(我需要一个带有一些字段黑名单的总和,但你可以轻松地将它调整为你的组/排序案例):https://stackoverflow.com/a/30813050/3793161。如果您需要更多详细信息,请发表评论

  3. 如果您计划拥有多台服务器,请注意每台服务器都必须进行观察,因此可能会导致不必要的负载。所以我的第三个解决方案是使用集合挂钩或方法来更新包含每天/用户的每个事件的附加字段(无论您需要什么)。请参阅@David Weldon 在这里回答:https://stackoverflow.com/a/31190896/3793161。在您的情况下,这可能意味着重新考虑您的数据库结构以满足您的需求(即添加更多字段,以便您可以在 insert 上更新它们。

编辑以下是对您评论的一些想法:

如果您坚持问题中描述的内容,您将需要七份文档,每天一份,其中包含一个 events 字段,您可以在其中放置所有事件。如果您需要在发送之前修改集合结构,我的第二个解决方案很好。但是,在您的情况下,您只需要一个对象week,其中包含 7 个与一周中的日期匹配的子对象。

我建议您采取可能的方法:

  • 在方法中使用聚合,如@chridam 所述。请注意,您将无法直接获得排序数组,如 mongo $group 文档中所述

$group 不对其输出文档进行排序

因此,在返回方法结果之前,您需要使用例如_.sortBy() 对它们进行排序(按天每天按小时)。顺便说一句,如果你想知道你的方法调用中发生了什么,clientside,你应该如何编写调用:

Meteor.call("getGroupedDailyEvents", userId, function(error, result){ 
    if(error){ 
        console.log("error", error); 
    } 
    if(result){ 
         //do whatever you need
    } 
});
  • 在客户端进行数据排序。您正在寻找一种矫枉过正的解决方案,因为,afaik,您不需要过滤任何数据以阻止用户访问,并且无论如何您都将发送数据(只是使用另一种结构)。像这样在模板中创建一个简单的助手要容易得多:

    Template.displaySchedule.helpers({
    
        "monday_events": function() {
            return _.sortBy (events.find({day:"Monday"}).fetch(), "time")
        },
        //add other days
    );
    

它假定您的time 字段的格式可以通过这种方式排序。如果没有,您只需要创建一个函数来根据它们的格式对它们进行排序,或者将原始格式更改为更适合的格式。

剩下的 (HTML) 将只是使用 {{#each monday_events}} 对星期一的事件进行迭代

【讨论】:

  • 就我而言,我不认为失去反应性是一个问题,所以如果我能做对,选项 1 会起作用。我也对选项 2 感兴趣,但并不真正明白。我将继续阅读您的示例,并希望获得一个粗略的指导,说明它如何适用于我的案例。 3,它是一个小应用程序,不会在多个服务器上运行。
  • 你说得对,在这个例子中聚合方法是多余的。我在想这样做会简化其他地方的代码。由于它正在简化的代码并不是特别复杂,因此它绝对是最重要的。您的第二个选择是这样做的方法。不过对我来说是很好的学习练习,我将首先让聚合器示例工作。日期实际上存储为数字 1-7,因此我可以通过将“monday_events”替换为更通用的内容来进一步简化它,并传入一个整数,为每一天创建一个新行。
  • 我很高兴能帮上忙。如果您认为此答案解决了您的问题,请考虑对其进行验证。
【解决方案2】:

要获得所需的结果,请使用 aggregation framework,其中 $group 管道运算符将所有输入文档分组并应用累加器表达式 @987654323 @ 到组以获取事件数组。

您的管道将如下所示:

var Events = new Mongo.Collection('events');
var pipeline = [        
    {
        "$group": {
            "_id": "$day",
            "events": {
                "$push": {
                    "time": "$time"
                    "location": "$location"
                }
            }
        }
    },
    {
        "$project": {
            "_id": 0, "day": "$_id", "events": 1
        }
    }
];
var result = Events.aggregate(pipeline);
console.log(result)

您可以添加 meteorhacks:aggregate package 来实现 Meteor 中的聚合:

添加到您的应用中

meteor add meteorhacks:aggregate

由于此包在 Mongo.Collection 实例上公开了 .aggregate 方法,因此您可以定义一个获取聚合结果数组的方法。例如

if (Meteor.isServer) {
    var Events = new Mongo.Collection('events');
    Meteor.methods({
        getGroupedDailyEvents: function () {
            var pipeline = [        
                {
                    "$group": {
                        "_id": "$day",
                        "events": {
                            "$push": {
                                "time": "$time"
                                "location": "$location"
                            }
                        }
                    }
                },
                {
                    "$project": {
                        "_id": 0, "day": "$_id", "events": 1
                    }
                }
            ];
            var result = Events.aggregate(pipeline);
            return result;
        }
    }); 
}

if (Meteor.isClient) {
    Meteor.call('getGroupedDailyEvents', logResult);

    function logResult(err, res) {
        console.log("Result: ", res)
    }
}

【讨论】:

  • 这太好了,谢谢!我可以按照您的示例进行操作,但是当我调用该函数时,我没有得到响应。 Meteor.methods({ 'getTrainingList': function(){ var pipeline = [ { $group: { _id: '$day', training: { $push: { time: '$time', location: '$location' } } } }/*, { $project: { _id: 0, day: '$_id', training: 1 } }*/ ]; var result = TrainingList.aggregate(pipeline); return result; } });
  • @kinsey 你是怎么调用函数的?
  • 抱歉忽略那些开始/结束注释标签..包含代码
  • if (Meteor.isClient) { Meteor.call('getTrainingList'); }
  • 啊,好吧!我只是变得不确定。这看起来肯定可以解决我的问题。直到今晚我才能试一试。会让你知道。非常感谢!
猜你喜欢
  • 2023-03-09
  • 2018-05-16
  • 1970-01-01
  • 1970-01-01
  • 2021-05-19
  • 1970-01-01
  • 1970-01-01
  • 2020-10-07
  • 1970-01-01
相关资源
最近更新 更多