Golang中如何使用ORM通过单一查询从数据库检索自定义(虚拟)列并重构代码

Golang中如何使用ORM通过单一查询从数据库检索自定义(虚拟)列并重构代码 如何重构此代码以仅使用一次查询通过ORM从数据库检索自定义(虚拟)列

我正在学习Golang,尝试构建一个应用程序。

我已经编写了许多模型:

  • Player
  • Book
  • Score
  • Referee
  • Life
  • Team
  • Doc
  • Family

等等……

一个模型如下所示:

type Player struct {

  id   int

  name string

}

我正在使用一个ORM:Go-PG(https://github.com/go-pg/pg),之前我也尝试过GORM(https://github.com/jinzhu/gorm)。

我这样使用它:

db := database.New(config)

defer db.Close()

var players []Player

error := db.Model(&players).Select()

// handle error

它工作正常。

SQL查询是:

SELECT * FROM players

结果行如下:

+--------+----------+
| id     | name     |
+--------+----------+
| 1      | John     |
+--------+----------+
| 2      | Mike     |
+--------+----------+
| 3      | Bob      |
+--------+----------+

现在我需要从数据库查询一个或多个“虚拟”列,像这样:

'mysqlcalculation' AS mycalculation

所以我尝试使用:

query.Column("*").ColumnExpr("'mysqlcalculation' AS mycalculation")

这会生成一个正确的SQL查询:

SELECT *, 'mysqlcalculation' AS mycalculation FROM players

结果行如下:

+--------+----------+------------------+
| id     | name     | mycalculation    |
+--------+----------+------------------+
| 1      | John     | mysqlcalculation |
+--------+----------+------------------+
| 2      | Mike     | mysqlcalculation |
+--------+----------+------------------+
| 3      | Bob      | mysqlcalculation |
+--------+----------+------------------+

我这样做是因为我对mycalculation列中的结果感兴趣,在数据库中计算这个比在Go中方便得多;它可以是JSON或字符串。就是简单的数据。

现在我的ORM报错了:

PNC error="pg: can't find column=mycalculation in model=Player (try discard_unknown_columns)"

我能理解,因为现在go-pg不知道如何绑定数据,所以我现在使用:

func FindAllPlayers(ctx context.Context, pagination models.Pagination) ([]*models.Player, *string, error) {

  var cursor *string

  var playerWithCalculation []struct {

    Mycalculation  string

    models.Player

  }

  var players []Player

  // use pagination for changing query settings

  error := db.Model(&playerWithCalculation).Column("*").ColumnExpr("'mysqlcalculation' AS mycalculation").Select()

  // handle error

  cursor := playerWithCalculation[0].Mycalculation

  for i := range playerWithCalculation {

    players = append(players, &playerWithCalculation[i].Player)

  }

return players, cursor, nil

问题

我认为这很浪费且开销大。很多分配和循环。

而且,我有好几个模型、解析器和方法!

我能做什么?

如何改进这段代码和我的架构?

我认为一个可能的解决方案是运行两个查询,也许在两个不同的goroutine中。

但我不想为此运行两个查询。


更多关于Golang中如何使用ORM通过单一查询从数据库检索自定义(虚拟)列并重构代码的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何使用ORM通过单一查询从数据库检索自定义(虚拟)列并重构代码的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go-PG中,可以通过结构体嵌入和自定义扫描器来优化虚拟列的查询。以下是两种改进方案:

方案1:使用结构体嵌入和自定义扫描器

type PlayerWithCalculation struct {
    models.Player
    Mycalculation string `pg:"mycalculation,use_zero"`
}

func FindAllPlayers(ctx context.Context, pagination models.Pagination) ([]*models.Player, string, error) {
    var playersWithCalc []PlayerWithCalculation
    
    err := db.Model(&playersWithCalc).
        Column("*").
        ColumnExpr("'mysqlcalculation' AS mycalculation").
        Select()
    
    if err != nil {
        return nil, "", err
    }
    
    // 提取虚拟列值
    var cursor string
    if len(playersWithCalc) > 0 {
        cursor = playersWithCalc[0].Mycalculation
    }
    
    // 转换为原始Player切片
    players := make([]*models.Player, len(playersWithCalc))
    for i := range playersWithCalc {
        players[i] = &playersWithCalc[i].Player
    }
    
    return players, cursor, nil
}

方案2:使用自定义扫描接口

type PlayerWithVirtual struct {
    ID            int    `pg:"id"`
    Name          string `pg:"name"`
    Mycalculation string `pg:"mycalculation"`
}

// 实现pg.Scanner接口
func (p *PlayerWithVirtual) Scan(ctx context.Context, rd *pg.Reader, n int) error {
    if n < 0 {
        return nil
    }
    
    // 手动扫描字段
    scanner := pg.NewScanner(rd)
    for i := 0; i < n; i++ {
        name := scanner.NextColumn()
        switch name {
        case "id":
            p.ID = scanner.NextInt()
        case "name":
            p.Name = scanner.NextString()
        case "mycalculation":
            p.Mycalculation = scanner.NextString()
        default:
            scanner.Skip()
        }
    }
    return scanner.Error()
}

func FindAllPlayersOptimized(ctx context.Context) ([]PlayerWithVirtual, error) {
    var players []PlayerWithVirtual
    
    err := db.Model(&players).
        Column("id", "name").
        ColumnExpr("'mysqlcalculation' AS mycalculation").
        Select()
    
    return players, err
}

方案3:使用原始查询和结构体映射

type PlayerResult struct {
    ID            int    `pg:"id"`
    Name          string `pg:"name"`
    Mycalculation string `pg:"mycalculation"`
}

func FindAllPlayersRaw(ctx context.Context) ([]PlayerResult, error) {
    var results []PlayerResult
    
    _, err := db.Query(&results, `
        SELECT id, name, 'mysqlcalculation' AS mycalculation 
        FROM players
    `)
    
    return results, err
}

方案4:使用查询钩子(Query Hook)

type PlayerWithHook struct {
    models.Player
    VirtualFields map[string]interface{} `pg:"-"`
}

func (p *PlayerWithHook) AfterScan(ctx context.Context, rd *pg.Reader, n int) error {
    if p.VirtualFields == nil {
        p.VirtualFields = make(map[string]interface{})
    }
    
    // 这里可以处理额外的虚拟列
    // 需要根据实际查询动态处理
    return nil
}

func FindAllPlayersWithHook() ([]PlayerWithHook, error) {
    var players []PlayerWithHook
    
    err := db.Model(&players).
        Column("*").
        ColumnExpr("'mysqlcalculation' AS mycalculation").
        Select()
    
    return players, err
}

性能优化建议

对于大量数据,可以使用指针切片减少内存分配:

func FindAllPlayersBatch(ctx context.Context) ([]*models.Player, []string, error) {
    type result struct {
        models.Player
        Calc string `pg:"mycalculation"`
    }
    
    var results []result
    err := db.Model(&results).
        Column("*").
        ColumnExpr("'mysqlcalculation' AS mycalculation").
        Select()
    
    if err != nil {
        return nil, nil, err
    }
    
    players := make([]*models.Player, len(results))
    calculations := make([]string, len(results))
    
    for i := range results {
        players[i] = &results[i].Player
        calculations[i] = results[i].Calc
    }
    
    return players, calculations, nil
}

这些方案都能在单一查询中检索虚拟列,避免了额外的循环和内存分配。方案1是最简洁的,方案2提供了最大的灵活性,方案3适合复杂查询,方案4适合需要后处理的情况。

回到顶部