英文:
In Gorm how can I use preload and distinct together?
问题
我在我的Go代码中有以下嵌套结构体:
type Notification struct {
SerialNumber string `json:"SerialNumber,omitempty"`
Ts time.Time `json:"Ts,omitempty"`
Notificationkeys []*NotificationKeys `json:"Notificationkeys,omitempty" gorm:"foreignKey:NotificationID"`
ID uint64 `gorm:"primaryKey;autoIncrement:true" json:"-"`
}
type NotificationKeys struct {
Key string `json:"Key,omitempty"`
Value string `json:"Value,omitempty"`
ID uint64 `gorm:"primaryKey;autoIncrement:true" json:"-"`
}
我使用GORM来接收通知,使用以下函数:
type repo struct {
db *gorm.DB
}
func (r repo) GetNotifications(serialNumber string, ts time.Time) ([]*Notification, error) {
retval := []*Notification{}
if tx := tr.db.Model(&Notification{}).Preload(clause.Associations).Where("serial_number=? AND ts <= ?", serialNumber, ts).Order("ts desc").Limit(3).Find(&retval); tx.Error != nil {
return retval, tx.Error
}
return retval, nil
}
到目前为止,这段代码是可以工作的:GORM将最近的三个通知及其键值对加载到了retval
数组中。
然而,由于一个错误,我的数据库中有重复的通知,它们具有相同的时间戳。
SQL Server和GORM都提供了DISTINCT
函数来解决这个问题。我尝试在函数的不同位置进行更改,例如:
func (r repo) GetNotifications(serialNumber string, ts time.Time) ([]*Notification, error) {
retval := []*Notification{}
if tx := tr.db.Model(&Notification{}).Preload(clause.Associations).Where("serial_number=? AND ts <= ?", serialNumber, ts).Order("ts desc").Distinct("ts").Limit(3).Find(&retval); tx.Error != nil {
return retval, tx.Error
}
return retval, nil
}
现在,GORM不再返回具有相同时间戳的通知,但是我的NotificationKeys
不再被加载,数组是空的。这两个函数是否不能结合使用,还是我漏掉了其他什么东西?
英文:
I have following nested structs in my Go Code:
type Notification struct {
SerialNumber string `json:"SerialNumber,omitempty"`
Ts time.Time `json:"Ts,omitempty"`
Notificationkeys []*NotificationKeys `json:"Notificationkeys,omitempty" gorm:"foreignKey:NotificationID"`
ID uint64 `gorm:"primaryKey;autoIncrement:true" json:"-"`
}
type NotificationKeys struct {
Key string `json:"Key,omitempty"`
Value string `json:"Value,omitempty"`
ID uint64 `gorm:"primaryKey;autoIncrement:true" json:"-"`
}
I use GORM for receiving notifications with following function:
type repo struct {
db *gorm.DB
}
func (r repo) GetNotifications(serialNumber string, ts time.Time) ([]*Notification, error) {
retval := []*Notification{}
if tx := tr.db.Model(&Notification{}).Preload(clause.Associations).Where("serial_number=? AND ts <= ?", serialNumber, ts).Order("ts desc").Limit(3).Find(&retval); tx.Error != nil {
return retval, tx.Error
}
return retval, nil
}
Hitherto, this code is working: GORM loads my most recent three notifications with its key-value pairs into my retval array.
However, due to a bug I have notifications in my database that are dublicates and thus have idential timestamps.
Both SQL Server and GORM offers the DISTINCT
function for this. I tried changing my function at various places for example like this:
func (r repo) GetNotifications(serialNumber string, ts time.Time) ([]*Notification, error) {
retval := []*Notification{}
if tx := tr.db.Model(&Notification{}).Preload(clause.Associations).Where("serial_number=? AND ts <= ?", serialNumber, ts).Order("ts desc").Distinct("ts").Limit(3).Find(&retval); tx.Error != nil {
return retval, tx.Error
}
return retval, nil
}
GORM does not deliver any notifications with identical timestamp now, however my NotificationKeys are not loaded anymore, the array is empty.Are those two functions not combineable or am I missing anything else?
答案1
得分: 1
我能够使用以下代码实现您的目标。首先,让我分享代码,然后我将逐步介绍所有相关部分。
package main
import (
"fmt"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type Notification struct {
ID uint64 `gorm:"primaryKey;autoIncrement:true" json:"-"`
SerialNumber string `json:"CraneSerialNumber,omitempty"`
Ts time.Time `json:"Ts,omitempty"`
NotificationKeys []*NotificationKeys `json:"Notificationkeys,omitempty"`
}
type NotificationKeys struct {
ID uint64 `gorm:"primaryKey;autoIncrement:true" json:"-"`
Key string `json:"Key,omitempty"`
Value string `json:"Value,omitempty"`
NotificationId uint64
}
func main() {
dsn := "host=localhost port=54322 user=postgres password=postgres dbname=postgres sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn))
if err != nil {
panic(err)
}
db.AutoMigrate(&Notification{}, &NotificationKeys{})
// 添加一些虚拟数据
db.Create(&Notification{ID: 1, SerialNumber: "001", Ts: time.Date(2023, 7, 1, 10, 30, 0, 0, time.UTC)})
db.Create(&Notification{ID: 2, SerialNumber: "001", Ts: time.Date(2023, 7, 1, 10, 30, 0, 0, time.UTC)})
db.Create(&Notification{ID: 3, SerialNumber: "003", Ts: time.Date(2023, 7, 1, 10, 35, 0, 0, time.UTC)})
db.Create(&NotificationKeys{Key: "a", Value: "1", NotificationId: 1})
db.Create(&NotificationKeys{Key: "b", Value: "1", NotificationId: 2})
db.Create(&NotificationKeys{Key: "c", Value: "1", NotificationId: 3})
// 获取逻辑
var notifications []Notification
err = db.Debug().
Model(&Notification{}).
Preload(clause.Associations).
Where("id in (?)", db.Debug().Model(&Notification{}).Group("serial_number,ts").Select("min(id) as id").Limit(2)). // 子查询
Order("ts desc").
Find(¬ifications).Error
if err != nil {
panic(err)
}
for _, v := range notifications {
fmt.Println(v)
for _, vv := range v.NotificationKeys {
fmt.Println(*vv)
}
}
}
代码主要分为三个部分。
结构体定义
我不得不调整NotificationKeys
结构体的定义,添加了以下字段:
NotificationId uint64
其余的代码应该没问题。
引导逻辑
您可以忽略它。我通过Docker运行了一个Postgres容器。然后,我创建了一个Gorm数据库客户端进行交互。此外,我向数据库添加了一些虚拟数据,以便能够重现问题。
获取逻辑
我通过使用以下方法解决了问题(只提到了最相关的方法):
- Preload:用于加载关联的
notifications
关系 - Subquery:内联编写,但您可以从
Where
中推断出来。这将通过serial_number
和ts
对notifications
表的记录进行分组,从而过滤掉重复的记录
通过将此查询拆分为两个较小的查询,您应该能够实现您的目标。
最后的代码只是为了循环遍历我们得到的结果。
如果一切正常,或者如果您需要其他帮助,请告诉我,谢谢!
英文:
I was able to achieve your goal with the following code. First, let me share the code, and then I'll walk you through all of the relevant sections.
package main
import (
"fmt"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type Notification struct {
ID uint64 `gorm:"primaryKey;autoIncrement:true" json:"-"`
SerialNumber string `json:"CraneSerialNumber,omitempty"`
Ts time.Time `json:"Ts,omitempty"`
NotificationKeys []*NotificationKeys `json:"Notificationkeys,omitempty"`
}
type NotificationKeys struct {
ID uint64 `gorm:"primaryKey;autoIncrement:true" json:"-"`
Key string `json:"Key,omitempty"`
Value string `json:"Value,omitempty"`
NotificationId uint64
}
func main() {
dsn := "host=localhost port=54322 user=postgres password=postgres dbname=postgres sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn))
if err != nil {
panic(err)
}
db.AutoMigrate(&Notification{}, &NotificationKeys{})
// seed some dummy data
db.Create(&Notification{ID: 1, SerialNumber: "001", Ts: time.Date(2023, 7, 1, 10, 30, 0, 0, time.UTC)})
db.Create(&Notification{ID: 2, SerialNumber: "001", Ts: time.Date(2023, 7, 1, 10, 30, 0, 0, time.UTC)})
db.Create(&Notification{ID: 3, SerialNumber: "003", Ts: time.Date(2023, 7, 1, 10, 35, 0, 0, time.UTC)})
db.Create(&NotificationKeys{Key: "a", Value: "1", NotificationId: 1})
db.Create(&NotificationKeys{Key: "b", Value: "1", NotificationId: 2})
db.Create(&NotificationKeys{Key: "c", Value: "1", NotificationId: 3})
// get logic
var notifications []Notification
err = db.Debug().
Model(&Notification{}).
Preload(clause.Associations).
Where("id in (?)", db.Debug().Model(&Notification{}).Group("serial_number,ts").Select("min(id) as id").Limit(2)). // subquery
Order("ts desc").
Find(&notifications).Error
if err != nil {
panic(err)
}
for _, v := range notifications {
fmt.Println(v)
for _, vv := range v.NotificationKeys {
fmt.Println(*vv)
}
}
}
The code is mainly divided into three sections.
The structs definition
I had to adjust the definition of the NotificationKeys
struct by adding the following field:
NotificationId uint64
The remaining code should be okay.
Bootstrap logic
You can merely ignore it. I run a Postgres container via Docker. Then, I created a Gorm DB client to interact with. Plus, I added some dummy data to the DB to be able to repro the issue.
The get logic
I was able to solve the issue by using the following methods (only the most relevant have been mentioned):
- Preload: to load the relationship
notifications
- Subquery: written inline but you can extrapolate it from the
Where
. This groups the record of thenotifications
table byserial_number
andts
thus filtering out the duplicated records
Thanks to this query that you can split into two smaller ones, you should be able to achieve your goal.
The final code has been added just to loop through the results we got.
Let me know if everything is okay or if you need something else, thanks!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论