I'm having troubles with one-to-many associations in GORM.
I have those two structures and I'd like to get one patient's full history. Here is my sample code:
type Patient struct {
gorm.Model
Prenom string `json:"prenom" gorm:"column:patient_prenom"`
Nom string `json:"nom" gorm:"column:patient_nom"`
Genre string `json:"genre" gorm:"column:patient_genre"`
Naissance string `json:"naissance" gorm:"column:patient_naissance"`
Historique []Historique `gorm:"ForeignKey:Fk_patient_id"`
}
type Historique struct {
Fk_patient_id string
Date_consultation string
Fk_maladie_id uint
Fk_compte_medecin_id uint
Patient Patient
}
func GetPatientWithDiseases(id uint) (*Patient, error) {
patient := &Patient{}
//The line right there works so i can retrieve without the history
//err := GetDB().Find(patient, id).Error
db := GetDB().Preload("tt_historique").Find(patient)
err := db.Error
if err != nil {
return nil, err
}
return patient, nil
}
Where "Historique" uses the foreign key of the patient (Fk_patient_id), and the Historique []Historique is the list of every Historique that should end up in the Patient struct after the query.
However I get this error can't preload field tt_historique for models.Patient. I've tried multiple syntaxes that I've found on Internet in the gorm specifications in the struct but nothing worked. I've only been developing using GO for 3 days and GORM is my first ORM, so maybe I'm missing something really obvious.
Based on the presumption that tt_historique is your table name, there are a couple of things you need to take care of here.
By convention, go-gorm uses pluralized snake case struct names as database tables when constructing a SQL query. In your case, to preload the Historique []Historique field, it would look for the historiques table.
To override this, you need to implement the Tabler interface:
type Patient struct {
gorm.Model
Prenom string `json:"prenom" gorm:"column:patient_prenom"`
Nom string `json:"nom" gorm:"column:patient_nom"`
Genre string `json:"genre" gorm:"column:patient_genre"`
Naissance string `json:"naissance" gorm:"column:patient_naissance"`
Historique []Historique `gorm:"foreignKey:Fk_patient_id"`
}
type Historique struct {
Fk_patient_id string
Date_consultation string
Fk_maladie_id uint
Fk_compte_medecin_id uint
Patient Patient
}
func (Historique) TableName() string {
return "tt_historique"
}
Then, your query would look like this:
db := GetDB().Preload("Historique").Find(patient)
Related
I have two models as follows:
type OHLCV struct {
gorm.Model
Interval string `gorm:"uniqueIndex:idx_ohlcv"`
Pair string `gorm:"uniqueIndex:idx_ohlcv"`
OpenTime time.Time `gorm:"uniqueIndex:idx_ohlcv"`
CloseTime time.Time `gorm:"uniqueIndex:idx_ohlcv"`
Open float64 `json:"open"`
High float64 `json:"high"`
Low float64 `json:"low"`
Close float64 `json:"close"`
Volume float64 `json:"volume"`
QuoteAssetVolume float64 `json:"quoteAssetVolume"`
NumberOfTrades float64 `json:"numberOfTrades"`
Calculations []Calculation `gorm:"foreignKey:OhlcvRefer"`
}
and
type Calculation struct {
gorm.Model
OhlcvRefer uint `gorm:"uniqueIndex:idx_calculation"`
Key string `gorm:"uniqueIndex:idx_calculation"`
Config string `gorm:"uniqueIndex:idx_calculation"`
Value float64
}
As you see both tables have unique indexes to prevent inserting duplicate data. The first table foreignKey is a part of the second table's unique index. The problem is how can I handle ON CONFLICT DO NOTHING behavior for both tables with a single GORM Create statement?
Before adding the Calculation association I was able handle CONFLICTS with
err = db.Clauses(clause.OnConflict{DoNothing: true,
Columns: []clause.Column{{Name: "interval"}, {Name: "pair"}, {Name: "open_time"}, {Name: "close_time"}},
}).Create(ohlcvs).Error
But now I get the following error:
ERROR: duplicate key value violates unique constraint "idx_calculation" (SQLSTATE 23505)
What I need is to DO NOTHING for the Calculation conflicts as well.
To achieve what you need, it should be enough to use the Unique index constraint on the two structs. Let's see how you can implement it.
package main
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type User struct {
Id int
Name string `gorm:"uniqueIndex:idx_name"`
Posts []Post
}
type Post struct {
Id int
Title string `gorm:"uniqueIndex:idx_title"`
UserId int
}
func main() {
dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Post{})
db.AutoMigrate(&User{})
db.Create(&User{
Name: "john doe",
Posts: []Post{
{Title: "first"},
{Title: "second"}, // to generate an error change to "first"
},
})
}
In this way, if you're entering duplicates value the db itself will block you. This is valid ono either on the users table and on the posts one. IMO, it's a very clean approach and you can be as flexible as you wish.
Let me know if this solves your issue or there is something else!
I'm trying to understand how to use GORM to make query on items with many2many relations but I'm really lost.
I've got the following database model:
type Asset struct {
gorm.Model
Id uint `gorm:"primaryKey"`
MachineUID string `gorm:"type:varchar(128)" json:"machine_uid"`
AssetToken string `gorm:"uniqueIndex;type:varchar(128)"`
CommandQueries []*CommandQuery `gorm:"many2many:command_asset;"`
}
type CommandQuery struct {
gorm.Model
Id uint `gorm:"primaryKey"`
UUID string `gorm:"type:varchar(128)" json:"uuid"`
CmdType int `json:"cmdtype"`
CmdArgs string `gorm:"type:varchar(128)" json:"cmdargs"`
Assets *[]Asset `gorm:"many2many:command_asset;"`
Active bool
}
First, i'm successfully trying to retrieve an asset from a token with something like this:
token := "test-token"
var result Asset
db.Where("asset_token = ?", token).First(&result)
if result.Id == 0 {
return fmt.Errorf("Asset cannot be found in database")
}
But fom this returned struct, i would like to retrive all CommandQuery objects where:
this asset is in CommandQuery.assets
Where CommandQuery.active = true
I tried many things but nothing works, any help would be appreciated.
If I understood correctly, you want to load a slice of CommandQuery objects, and these objects should contain only assets where asset_token should be equal to the token you passed. Also, return only CommanQuery objects that have active=true.
If this is the case, it can be done like this:
token := "test-token"
var list []CommandQuery{}
tx := db.Preload("Assets", func (gdb *gorm.DB) *gorm.DB{
return gdb.Where("asset_token = ?", token)
}).
Where("active = ?", true).Find(&list)
if tx.Error == nil {
return nil, tx.Error
}
return *list, nil
In short, custom preloading is used to load assets into command query objects.
I am trying to pull data for CountyEntity that related with CityEntity:
type CityEntity struct {
tableName struct{} `pg:"city,alias:ci,discard_unknown_columns"`
Id string `pg:"id"`
Name string `pg:"name"`
}
type CountyEntity struct {
tableName struct{} `pg:"county,alias:co,discard_unknown_columns"`
Id int64 `pg:"id"`
Name string `pg:"name"`
DefaultTargetXDockId int64 `pg:"defaulttargetxdockid"`
MapsPreference string `pg:"mapspreference"`
EmptyDistrictAllowed bool `pg:"empty_district_allowed"`
CityId int64 `pg:"cityid"`
City *CityEntity `pg:"fk:cityid"`
}
My query is:
db, _ := repository.pgRepository.CreateDBConnection(.....)
var counties []model.CountyEntity
err := db.Model(&counties).
Join("inner join test.city ci on ci.id = co.cityid").
Relation("City").
Where("co.cityid = ?", cityId).
Select()
return counties, err
it throws that:
model=CountyEntity does not have relation="City"
But actually, I have the relation between city and county table on the database.
Db Relation Image
I tried different ways to solve but I couldn't solve it. Does anybody have an idea about what is the root cause of it and what is the possible solutions?
Pretty old and I'm sure you've figured it out by now, but for anyone else that stumbles across this your model should look like this
type CountyEntity struct {
tableName struct{} `pg:"county,alias:co,discard_unknown_columns"`
Id int64 `pg:"id"`
Name string `pg:"name"`
DefaultTargetXDockId int64 `pg:"defaulttargetxdockid"`
MapsPreference string `pg:"mapspreference"`
EmptyDistrictAllowed bool `pg:"empty_district_allowed"`
CityId int64 `pg:"cityid"`
City *CityEntity `pg:"rel:has-one,fk:cityid"`
}
Notice the "rel:has-one" added to the city column. You can find more information about all of these here: https://pg.uptrace.dev/models/
I have these two structures:
type Collection struct {
gorm.Model
APIKey string
CollectionID string
Name string
Environments []Environment
}
type Environment struct {
gorm.Model
EnvironmentID string
Name string
Provider string
FlightType string
ADT int
CHD int
INF int
}
And the main looks like:
func main() {
adminResource := admin.New(&admin.AdminConfig{DB: model.DB})
adminResource.AddResource(&model.Collection{})
adminResource.AddResource(&model.Environment{})
mux := http.NewServeMux()
adminResource.MountTo("/admin", mux)
if err := http.ListenAndServe(":8000", mux); err != nil {
panic(err)
} else {
fmt.Println("Listening on: 8000")
}
}
The point is that I donĀ“t know if I should to do something else to allow the membership relationship one-to-many between the Collection and the Environemt. The thing is that the form on the admin view looks good, I can add as many environments as I was... but submitting the form only the Collection are saved on the database.
The thing, which is missing is to tell gorm the foreign key of the other model.
In your case we use Has Many (gorm Doc). To define the relationship you have to add a tag to your struct:
type Collection struct {
gorm.Model
APIKey string
CollectionID string
Name string
Environments []Environment `gorm:"foreignkey:EnvironmentID"`
}
type Environment struct {
gorm.Model
EnvironmentID string
Name string
Provider string
FlightType string
ADT int
CHD int
INF int
}
Without defining the foreign key of the other model gorm is not able to match those both models. As the convention for the primary key is ID and your Enviroment does not have that field it is not possible to match something. Be sure to read the documentation about the conventions.
Finally I found the solution.
type Collection struct {
gorm.Model
APIKey string
CollectionID string
Name string
Environments []Environment
}
type Environment struct {
gorm.Model
EnvironmentID string
Name string
Provider string
FlightType string
ADT int
CHD int
INF int
CollectionID int
}
Adding CollectionID int on the Environment struct is enough... so simply :D.
Thanks #apxp
The idea is to design a table/entity that contains some basic info, as well as a Markdown-Content field that would allow users to easily create tables and such.
I'm thinking something like this:
type Tournament struct {
ID in64 `datastore:"-"`
MDContent []byte `datastore:",noindex"`
Name string
URL string
DateCreated int64
CreatedBy string
DateUpdated int64
UpdatedBy string
ApprovalStatus int64 // 0=to be decided, 1=approved, 2=rejected, 3=discontinued
ApprovalBy string
}
My problem is figuring out how to update it. The ID field will also be used as the URL path, e.g. if an entity has ID 7 then it will be displayed on example.com/tournament/7.
I believe this eliminates the possibility of simply creating a new entity with updated data, and then set the ApprovalStatus=3 on the previous entity, because if you do as such then the example.com/tournament/7 URL will no longer request the correct ID.
I also don't like the idea of creating my own unique ID because I think it would be great to simply take advantage of the Datastore ID generation (which also makes it easy to get the correct entity based on URL); I considered creating a new entity/table that would keep track of revisions but I'm not sure how efficient all of this is, so I was hoping some expert might be able to give some advice.
Update related to #mkopriva solution:
If you do it this way, then it's necessary to include a TournamentID field inside the TournamentEdit entity struct I think?
type TournamentEdit struct {
ID in64 `datastore:"-"`
TournamentID int64
MDContent []byte `datastore:",noindex"`
DateCreated int64
CreatedBy string
ApprovalStatus int64 // 0=to be decided, 1=approved, 2=rejected, 3=discontinued
ApprovalBy string
}
And then the retrieve function could look like this:
func (db *datastoreDB) GetTournamentByKeyID(ctx context.Context, keyID int64) (*Tournament, error) {
key := datastore.IDKey("Tournament", keyID, nil)
var tournamnet Tournament
err := db.client.Get(ctx, key, &tournament)
// err checking
tournament.ID = key.ID
var edits TournamentEdits
query := datastore.NewQuery("TournamentEdit")
query = query.Filter("TournamentID =", tournament.ID)
query = query.Filter("ApprovalStatus =", 1)
if _, err := db.client.GetAll(ctx, query, &edits); err != nil {
//err checking
}
tournament.Edits = edits // I guess this is wrong way to do it?
return &tournament, nil
}
Would this work?
One thing you could do is to simply create a new entity that would represent the edit of a tournament. By the way, I'm not a datastore user so I'm not sure if this is how you would model the entities but the general idea is the same for most, if not all, databases:
type Tournament struct {
ID in64 `datastore:"-"`
MDContent []byte `datastore:",noindex"`
Name string
URL string
DateCreated int64
CreatedBy string
DateUpdated int64
UpdatedBy string
Edits []TournamentEdit
}
type TournamentEdit struct {
ID in64 `datastore:"-"`
MDContent []byte `datastore:",noindex"`
DateCreated int64
CreatedBy string
ApprovalStatus int64 // 0=to be decided, 1=approved, 2=rejected, 3=discontinued
ApprovalBy string
}
This should allow you to have multiple edits from different users in the queue, CRUD a specific edit, and or filter edits by their status.