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
更多关于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适合需要后处理的情况。

