如何在GORM中仅返回满足预加载条件的关联非空记录?
问题描述
现有一对多关联的两张表(User和Email),使用GORM操作数据库时,当前通过Preload("Emails", "address like 'user_1@%'")查询后,会返回所有用户——符合邮箱条件的用户会加载对应的Emails,不符合的用户Emails字段为空。现在希望直接通过GORM实现筛选,只返回符合Preload中指定邮箱条件的用户,而非后续在内存中过滤。
示例代码
package main import ( "github.com/davecgh/go-spew/spew" "github.com/gofrs/uuid" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/clause" ) type Email struct { ID uuid.UUID `gorm:"primary_key;type:char(36);"` Address string `gorm:"unique"` Uid uuid.UUID } type User struct { ID uuid.UUID `gorm:"primary_key;type:char(36);"` Name string `gorm:"unique"` Emails []Email `gorm:"ForeignKey:Uid"` } func main() { var err error var db *gorm.DB db, err = gorm.Open(mysql.New(mysql.Config{ DSN: "root:AsdfgH123@tcp(localhost:3306)/maindb?charset=utf8&parseTime=True&loc=Local", }), &gorm.Config{}) if err != nil { panic(err) } err = db.AutoMigrate(&User{}, &Email{}) if err != nil { panic(err) } uid1, _ := uuid.NewV7() uid2, _ := uuid.NewV7() eid1, _ := uuid.NewV7() eid2, _ := uuid.NewV7() users := []User{ {Name: "user_1", ID: uid1, Emails: []Email{{ID: eid1, Address: "user_1@example.com"}}}, {Name: "user_2", ID: uid2, Emails: []Email{{ID: eid2, Address: "user_2@example.com"}}}, } db.Clauses(clause.Insert{Modifier: "IGNORE"}).Create(&users) dbUsers := []User{} db.Preload("Emails", "address like 'user_1@%'").Find(&dbUsers) spew.Dump(dbUsers) }
执行结果
([]main.User) (len=2 cap=20) { (main.User) { ID: (uuid.UUID) (len=16 cap=16) 01833180-a4f4-7663-a73e-798b110724ed, Name: (string) (len=6) "user_1", Emails: ([]main.Email) (len=1 cap=10) { (main.Email) { ID: (uuid.UUID) (len=16 cap=16) 01833180-a4f4-70e6-ad25-c5726212a97c, Address: (string) (len=18) "user_1@example.com", Uid: (uuid.UUID) (len=16 cap=16) 01833180-a4f4-7663-a73e-798b110724ed } } }, (main.User) { ID: (uuid.UUID) (len=16 cap=16) 01833180-a4f4-738f-ad46-1c7bb36cf79c, Name: (string) (len=6) "user_2", Emails: ([]main.Email) (cap=10) { } } }
需要解决的核心问题:能否直接通过GORM实现筛选,仅返回符合Preload中指定邮箱条件的用户?
解决方案
可以实现,Preload仅负责关联数据的预加载,不会过滤主表数据。要只返回符合邮箱条件的用户,需要结合关联查询逻辑过滤主表,同时保留预加载,具体有两种常用方式:
方式一:使用Joins关联过滤
通过关联Email表,直接在主查询中筛选出拥有符合条件邮箱的用户,再预加载对应邮箱数据:
dbUsers := []User{} db.Joins("JOIN emails ON users.id = emails.uid AND emails.address LIKE ?", "user_1@%"). Preload("Emails", "address LIKE ?", "user_1@%"). Find(&dbUsers) spew.Dump(dbUsers)
该方式会生成关联查询,主查询仅返回符合条件的用户,同时预加载对应的邮箱数据。
方式二:使用EXISTS子查询过滤
利用Where结合EXISTS子查询,筛选出存在符合条件邮箱的用户,再预加载对应数据:
dbUsers := []User{} db.Preload("Emails", "address LIKE ?", "user_1@%"). Where("EXISTS (SELECT 1 FROM emails WHERE emails.uid = users.id AND emails.address LIKE ?)", "user_1@%"). Find(&dbUsers) spew.Dump(dbUsers)
这种方式通过子查询判断用户是否存在符合条件的邮箱,仅保留符合条件的用户,同时预加载对应邮箱数据。
注意事项
- 两种方式生成的SQL不同,可根据业务场景选择;
- 必须使用参数占位符
?绑定条件值,避免SQL注入风险。
内容的提问来源于stack exchange,提问作者Pavel Perminov
相关产品推荐
相关产品推荐

