Golang ORM实现单行数据到多个关联模型的转换

Golang ORM实现单行数据到多个关联模型的转换

你好。

数据库中有三个关联的模型(如下所述)。我们需要通过单次数据库查询(严格的时间限制)来迭代所有具有合适尺寸且处于活跃广告活动中的广告。

编写一个SQL查询,让每行同时包含广告和广告活动信息是很容易的。但我无法想出一种简单且可维护的方法来将这样的数据行映射到模型`Ad`、`AdGroup`和`Campaign`,以便在代码中以`Ad.AdGroup.Campaigns[0]`的形式访问它们(或者更好的形式是`Ad.Campaign`或`Ad.AdGroup.Campaign`,因为我们只需要选定的一个组合,而不是该`Ad`所有可用的`Campaigns`)。

哪种ORM可以在不创建包含所有模型必要字段的"弗兰肯斯坦"模型并将数据行映射到它的情况下,帮助实现这一目标?

```go
type Ad struct {
    Id int
    Height int
    Width int
    AdGroupId int
    // 每个广告仅属于一个广告组
    AdGroup AdGroup 
}
type AdGroup struct {
    Id int
    // 广告组中包含多个广告
    Ads []Ad
}
type Campaign struct {
    Id int
    Active bool
    // Campaign与AdGroup的多对多关系
    AdGroups []AdGroup
}
type AdGroupCampaign {
    AdGroupId int
    AdGroup AdGroup
    CampaignId int
    Campaign Campaign
}

更多关于Golang ORM实现单行数据到多个关联模型的转换的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

sqlboiler 在这里很适用。

操作指南:YouTubeGitHub

更多关于Golang ORM实现单行数据到多个关联模型的转换的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go语言中,使用GORM可以很好地处理这种关联模型的转换。通过预加载(Preload)和自定义查询,可以在单次数据库查询中获取所有相关数据,并将其映射到对应的模型结构中。

以下是使用GORM的实现方案:

package main

import (
    "fmt"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

type Ad struct {
    ID        int
    Height    int
    Width     int
    AdGroupID int
    AdGroup   AdGroup
}

type AdGroup struct {
    ID       int
    Ads      []Ad
    Campaign Campaign `gorm:"-"`
}

type Campaign struct {
    ID       int
    Active   bool
    AdGroups []AdGroup `gorm:"-"`
}

type AdGroupCampaign struct {
    AdGroupID  int
    CampaignID int
}

func main() {
    dsn := "host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable"
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    var ads []Ad
    
    // 单次查询获取所有相关数据
    err = db.
        Preload("AdGroup").
        Joins("JOIN ad_groups ON ads.ad_group_id = ad_groups.id").
        Joins("JOIN ad_group_campaigns ON ad_groups.id = ad_group_campaigns.ad_group_id").
        Joins("JOIN campaigns ON ad_group_campaigns.campaign_id = campaigns.id").
        Where("ads.height >= ? AND ads.width >= ? AND campaigns.active = ?", 300, 250, true).
        Find(&ads).Error
    
    if err != nil {
        panic(err)
    }

    // 手动建立AdGroup到Campaign的关联
    for i := range ads {
        var campaign Campaign
        db.Table("campaigns").
            Joins("JOIN ad_group_campaigns ON campaigns.id = ad_group_campaigns.campaign_id").
            Where("ad_group_campaigns.ad_group_id = ?", ads[i].AdGroupID).
            First(&campaign)
        
        ads[i].AdGroup.Campaign = campaign
    }

    // 访问示例
    for _, ad := range ads {
        fmt.Printf("Ad ID: %d, Campaign: %+v\n", ad.ID, ad.AdGroup.Campaign)
        // 访问方式:ad.AdGroup.Campaign
    }
}

更优化的方案是使用自定义查询和结构体扫描:

type AdWithRelations struct {
    Ad
    AdGroupID   int
    CampaignID  int
    CampaignActive bool
}

func getAdsWithRelations(db *gorm.DB) ([]Ad, error) {
    var results []AdWithRelations
    
    err := db.Table("ads").
        Select(`
            ads.*,
            ad_groups.id as ad_group_id,
            campaigns.id as campaign_id,
            campaigns.active as campaign_active
        `).
        Joins("JOIN ad_groups ON ads.ad_group_id = ad_groups.id").
        Joins("JOIN ad_group_campaigns ON ad_groups.id = ad_group_campaigns.ad_group_id").
        Joins("JOIN campaigns ON ad_group_campaigns.campaign_id = campaigns.id").
        Where("ads.height >= ? AND ads.width >= ? AND campaigns.active = ?", 300, 250, true).
        Scan(&results).Error
    
    if err != nil {
        return nil, err
    }

    // 转换为目标结构
    var ads []Ad
    for _, result := range results {
        ad := Ad{
            ID:        result.Ad.ID,
            Height:    result.Height,
            Width:     result.Width,
            AdGroupID: result.AdGroupID,
            AdGroup: AdGroup{
                ID: result.AdGroupID,
                Campaign: Campaign{
                    ID:     result.CampaignID,
                    Active: result.CampaignActive,
                },
            },
        }
        ads = append(ads, ad)
    }
    
    return ads, nil
}

使用示例:

ads, err := getAdsWithRelations(db)
if err != nil {
    panic(err)
}

// 直接访问:ad.AdGroup.Campaign
for _, ad := range ads {
    if ad.AdGroup.Campaign.Active {
        fmt.Printf("Ad %d belongs to active campaign %d\n", 
            ad.ID, ad.AdGroup.Campaign.ID)
    }
}

这种方法通过单次查询获取所有必要数据,然后手动构建对象关系,避免了创建复杂的"弗兰肯斯坦"模型,同时保持了代码的可维护性和性能要求。

回到顶部