【问题标题】:Get a value in a reference of a lookup with MongoDB and Golang在使用 MongoDB 和 Golang 的查找引用中获取值
【发布时间】:2018-01-10 21:31:16
【问题描述】:

我有以下结构。我使用 Golang 1.9.2

// EventBoost describes the model of a EventBoost
type EventBoost struct {
  ID          string    `bson:"_id" json:"_id" valid:"alphanum,printableascii"`
  CampaignID  string    `bson:"_campaign_id" json:"_campaign_id" valid:"alphanum,printableascii"`
  Name        string    `bson:"name" json:"name"`
  Description string    `bson:"description" json:"description"`
  Level       string    `bson:"level" json:"level"`
  EventID     string    `bson:"_event_id" json:"_event_id" valid:"alphanum,printableascii"`
  StartDate   time.Time `bson:"start_date" json:"start_date"`
  EndDate     time.Time `bson:"end_date" json:"end_date"`
  IsPublished bool      `bson:"is_published" json:"is_published"`
  CreatedBy   string    `bson:"created_by" json:"created_by"`
  CreatedAt   time.Time `bson:"created_at" json:"created_at"`
  ModifiedAt  time.Time `bson:"modified_at" json:"modified_at"`
}

// LocationBoost describes the model of a LocationBoost
type LocationBoost struct {
  ID          string    `bson:"_id" json:"_id" valid:"alphanum,printableascii"`
  CampaignID  string    `bson:"_campaign_id" json:"_campaign_id" valid:"alphanum,printableascii"`
  Name        string    `bson:"name" json:"name"`
  Description string    `bson:"description" json:"description"`
  Level       string    `bson:"level" json:"level"`
  LocationID  string    `bson:"_location_id" json:"_location_id" valid:"alphanum,printableascii"`
  StartDate   time.Time `bson:"start_date" json:"start_date"`
  EndDate     time.Time `bson:"end_date" json:"end_date"`
  IsPublished bool      `bson:"is_published" json:"is_published"`
  CreatedBy   string    `bson:"created_by" json:"created_by"`
  CreatedAt   time.Time `bson:"created_at" json:"created_at"`
  ModifiedAt  time.Time `bson:"modified_at" json:"modified_at"`
}

// Campaign describes the model of a Campaign
type Campaign struct {
    ID               string           `bson:"_id" json:"_id" valid:"alphanum,printableascii"`
    Name             string           `bson:"name" json:"name"`
    Description      string           `bson:"description" json:"description"`
    EventBoostIDs    []string         `bson:"event_boost_ids" json:"event_boost_ids"`
    LocationBoostIDs []string         `bson:"location_boost_ids" json:"location_boost_ids"`
    StartDate        time.Time        `bson:"start_date" json:"start_date"`
    EndDate          time.Time        `bson:"end_date" json:"end_date"`
    IsPublished      bool             `bson:"is_published" json:"is_published"`
    CreatedBy        string           `bson:"created_by" json:"created_by"`
    CreatedAt        time.Time        `bson:"created_at" json:"created_at"`
    ModifiedAt       time.Time        `bson:"modified_at" json:"modified_at"`
}

Campaign(了解营销活动)由 EventsLocations 组成,可以通过级别(基本或高级)提升.广告活动有开始日期和结束日期,提升也有。

GetEventLevel 函数必须返回给定事件的级别。

// GetEventLevel of an event
func (dao *campaignDAO) GetEventLevel(eventID string) (string, error) {
}

如果事件在一个活跃的活动isPublishedtrue)中被提升,并且提升是活跃的isPublishedtrue)并且现在的日期介于提升的开始日期和结束日期之间,那么我的事件是提升的,因此该函数返回级别(基本或高级)。否则返回"standard"

我的问题是:我可以用 Mongo 完全做到这一点吗?还是我需要在 DAO 中使用 Golang 执行一些逻辑?

如果我能用 Mongo 做到这一点,我希望,我不知道如何做到这一点。据我了解,我首先需要查找活动的事件和位置,然后在其中搜索日期,但是..

【问题讨论】:

  • 如果您的Campaign 不包含嵌入的EventBoosts 和LocationBoosts(只是它们的ID),那么它们的类型应该是string 的一部分(Campaign.EventBoostIDs 的类型和Campaign.LocationBoostIDs 应该是 []string)。您将它们存储在不同的集合中是否正确?或者你在你的 DAO 中填充这些?
  • 抱歉,我复制/粘贴错误。这些当然是字符串。让我编辑原始帖子。
  • 另外,将这些存储在Campaign 中有什么用?因为如果提升还存储了Campaign ID,那么可以通过过滤广告系列 ID 来查询它们?
  • CampaignID 可以从EventBoostLocationBoost 或Campaign 中的ID 中删除,这是正确的,但我现在想保留它。
  • 您期待什么样的答案? MongoDB(控制台)查询或如何在 Go 中执行(使用 mgo)?

标签: mongodb go mgo


【解决方案1】:

大部分(也是最困难的部分)可以在 MongoDB 中轻松完成。返回“基本”、“高级”或“标准”的最后一步很可能也可以完成,但我认为这不值得麻烦,因为这在 Go 中是微不足道的。

在 MongoDB 中,为此使用 Aggregation framework。这可以通过Collection.Pipe() 方法在mgo 包中获得。您必须将一个切片传递给它,每个元素对应一个聚合阶段。阅读此答案了解更多详情:How to Get an Aggregate from a MongoDB Collection

回到你的例子。你的GetEventLevel() 方法可以这样实现:

func (dao *campaignDAO) GetEventLevel(eventID string) (string, error) {
    c := sess.DB("").C("eventboosts") // sess represents a MongoDB Session
    now := time.Now()
    pipe := c.Pipe([]bson.M{
        {
            "$match": bson.M{
                "_event_id":    eventID,            // Boost for the specific event
                "is_published": true,               // Boost is active
                "start_date":   bson.M{"$lt": now}, // now is between start and end
                "end_date":     bson.M{"$gt": now}, // now is between start and end
            },
        },
        {
            "$lookup": bson.M{
                "from":         "campaigns",
                "localField":   "_campaign_id",
                "foreignField": "_id",
                "as":           "campaign",
            },
        },
        {"$unwind": "$campaign"},
        {
            "$match": bson.M{
                "campaign.is_published": true,      // Attached campaign is active
            },
        },
    })

    var result []*EventBoost
    if err := pipe.All(&result); err != nil {
        return "", err
    }
    if len(result) == 0 {
        return "standard", nil
    }
    return result[0].Level, nil
}

如果您最多只需要一个EventBoost(或者可能不会同时有更多),请使用$limit 阶段将结果限制为一个,并使用$project 仅获取@987654331 @字段,仅此而已。

使用此管道进行上述简化/优化:

pipe := c.Pipe([]bson.M{
    {
        "$match": bson.M{
            "_event_id":    eventID,            // Boost for the specific event
            "is_published": true,               // Boost is active
            "start_date":   bson.M{"$lt": now}, // now is between start and end
            "end_date":     bson.M{"$gt": now}, // now is between start and end
        },
    },
    {
        "$lookup": bson.M{
            "from":         "campaigns",
            "localField":   "_campaign_id",
            "foreignField": "_id",
            "as":           "campaign",
        },
    },
    {"$unwind": "$campaign"},
    {
        "$match": bson.M{
            "campaign.is_published": true,      // Attached campaign is active
        },
    },
    {"$limit": 1},             // Fetch at most 1 result
    {
        "$project": bson.M{
            "_id":   0,        // We don't even need the EventBoost's ID
            "level": "$level", // We do need the level and nothing more
        },
    },
})

【讨论】:

  • 我真的很感谢你,这是一个巨大的帮助!我还学到了一个新概念:$unwind。它正在工作......谢谢。
  • 如果我可以添加注释,您使用result[0].Level 以防有多个结果,这非常聪明。但是如果我确定一个事件不能同时被多次提升(保存时防止这样做),我们可以使用One() 来代替?
  • @Elwyn 我建议不要在这种情况下使用Pipe.One(),因为One() 期望得到结果。如果没有结果,它会返回一个错误(尽管您可以很容易地与mgo.ErrNotFound 进行比较)。所以我选择使用All(),如果没有结果,它不会返回错误。您还可以添加$limit 阶段以仅获取单个结果,也可以使用$project 仅返回level 并省略其余数据。
  • 感谢您的建议,我会看看我还不知道的$project
  • @Elwyn 添加了优化以获取最多 1 个结果,并排除除关卡之外的所有其他内容。请参阅编辑后的答案。
【解决方案2】:

由于您只存储 ID 以引用其他集合中的文档,而不是完全反规范化数据,不,您不能纯粹在 MongoDB 中执行此操作。 MongoDB 不是关系数据库。您所描述的正是 MongoDB 的设计目的

您需要在 Go 中执行逻辑;是否在 DAO 中这样做取决于您,但就个人而言,我倾向于一种简单的方法,该方法基于字段值动态执行逻辑,例如Campaign.GetEventLevel 或类似的东西。在你的 DAO 中拥有一个细粒度的方法对我来说意味着一些不适合 MongoDB 模型的不寻常的设计决策。在大多数情况下,使用 MongoDB,您希望检索您的文档(对象)并在您的应用程序中使用它们。尝试像使用典型的 RDBMS 一样在 MongoDB 中执行查询逻辑会导致挫败感和性能下降。

【讨论】:

  • 您好,如上面的答案所示,不正确。无论如何,关于您提到的关系,允许在 Mongo 中执行一些查找。我在我的软件中使用了称为 DTO 的东西,以便从 DAO 到 API 的对象在完整对象中查找。但出于很多原因,我更喜欢将它们存储为关系(在这种情况下,我也使用嵌入对象)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-08
  • 2021-12-09
  • 2012-04-26
  • 1970-01-01
  • 2019-02-03
  • 2019-06-04
  • 1970-01-01
相关资源
最近更新 更多