在Gorm中,你可以如何同时使用preload和distinct?

huangapple go评论101阅读模式
英文:

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:&quot;SerialNumber,omitempty&quot;`
   	Ts                time.Time `json:&quot;Ts,omitempty&quot;`
	Notificationkeys  []*NotificationKeys   `json:&quot;Notificationkeys,omitempty&quot; gorm:&quot;foreignKey:NotificationID&quot;` 
    ID                uint64                 `gorm:&quot;primaryKey;autoIncrement:true&quot; json:&quot;-&quot;`
}

type NotificationKeys struct {
     Key            string  `json:&quot;Key,omitempty&quot;`
     Value          string  `json:&quot;Value,omitempty&quot;`
     ID             uint64  `gorm:&quot;primaryKey;autoIncrement:true&quot; json:&quot;-&quot;`
}

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(&amp;Notification{}).Preload(clause.Associations).Where(&quot;serial_number=? AND ts &lt;= ?&quot;, serialNumber, ts).Order(&quot;ts desc&quot;).Limit(3).Find(&amp;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(&amp;Notification{}).Preload(clause.Associations).Where(&quot;serial_number=? AND ts &lt;= ?&quot;, serialNumber, ts).Order(&quot;ts desc&quot;).Distinct(&quot;ts&quot;).Limit(3).Find(&amp;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(&notifications).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_numbertsnotifications表的记录进行分组,从而过滤掉重复的记录

通过将此查询拆分为两个较小的查询,您应该能够实现您的目标。
最后的代码只是为了循环遍历我们得到的结果。

如果一切正常,或者如果您需要其他帮助,请告诉我,谢谢!

英文:

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 (
	&quot;fmt&quot;
	&quot;time&quot;

	&quot;gorm.io/driver/postgres&quot;
	&quot;gorm.io/gorm&quot;
	&quot;gorm.io/gorm/clause&quot;
)

type Notification struct {
	ID               uint64              `gorm:&quot;primaryKey;autoIncrement:true&quot; json:&quot;-&quot;`
	SerialNumber     string              `json:&quot;CraneSerialNumber,omitempty&quot;`
	Ts               time.Time           `json:&quot;Ts,omitempty&quot;`
	NotificationKeys []*NotificationKeys `json:&quot;Notificationkeys,omitempty&quot;`
}

type NotificationKeys struct {
	ID             uint64 `gorm:&quot;primaryKey;autoIncrement:true&quot; json:&quot;-&quot;`
	Key            string `json:&quot;Key,omitempty&quot;`
	Value          string `json:&quot;Value,omitempty&quot;`
	NotificationId uint64
}

func main() {
	dsn := &quot;host=localhost port=54322 user=postgres password=postgres dbname=postgres sslmode=disable&quot;
	db, err := gorm.Open(postgres.Open(dsn))
	if err != nil {
		panic(err)
	}
	db.AutoMigrate(&amp;Notification{}, &amp;NotificationKeys{})

	// seed some dummy data
	db.Create(&amp;Notification{ID: 1, SerialNumber: &quot;001&quot;, Ts: time.Date(2023, 7, 1, 10, 30, 0, 0, time.UTC)})
	db.Create(&amp;Notification{ID: 2, SerialNumber: &quot;001&quot;, Ts: time.Date(2023, 7, 1, 10, 30, 0, 0, time.UTC)})
	db.Create(&amp;Notification{ID: 3, SerialNumber: &quot;003&quot;, Ts: time.Date(2023, 7, 1, 10, 35, 0, 0, time.UTC)})
	db.Create(&amp;NotificationKeys{Key: &quot;a&quot;, Value: &quot;1&quot;, NotificationId: 1})
	db.Create(&amp;NotificationKeys{Key: &quot;b&quot;, Value: &quot;1&quot;, NotificationId: 2})
	db.Create(&amp;NotificationKeys{Key: &quot;c&quot;, Value: &quot;1&quot;, NotificationId: 3})

	// get logic
	var notifications []Notification
	err = db.Debug().
		Model(&amp;Notification{}).
		Preload(clause.Associations).
		Where(&quot;id in (?)&quot;, db.Debug().Model(&amp;Notification{}).Group(&quot;serial_number,ts&quot;).Select(&quot;min(id) as id&quot;).Limit(2)). // subquery
		Order(&quot;ts desc&quot;).
		Find(&amp;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 the notifications table by serial_number and ts 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!

huangapple
  • 本文由 发表于 2023年7月19日 16:08:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76719177.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定