How to search on a many-to-many field - go

Let's assume I have the below structs
type Job struct {
ID string `sql:"type:uuid;primary_key;"`
Title string `json:"title,omitempty"`
Skills []*skill.Skill `json:"skills,omitempty"gorm:"many2many:job_skill;"`
}
type Skill struct {
Name string `json:"name" gorm:"primary_key"`
}
To query all the jobs I do:
jobs := &[]Job{}
gorm.DB.Preload("Skills").Find(&jobs)
How do I search for a Job that contains a certain skill? I have tried the below but it says the column does not exist.
s := "golang"
jobs := &[]Job{}
gorm.DB.Preload("Skills").Where("skill = ?", s).Find(&jobs)
I can see my issues, = doesn't seem to be the correct operator as it needs to search in a list. And it also isn't loading the join table as I assumed it would
Debug output
pq: column "skill" does not exist
SELECT * FROM "jobs" WHERE skill = 'golang'

The Preload method and the Associations feature help you load your fields by constructing basic SQL queries based on the relationships you have created. Queries like loading all skills for a specific job (or jobs). But it doesn't go any more complex than that.
Rather, think of go-gorm as a way to construct your SQL queries and load the data into your models.
Having that in mind, one solution would be to use Joins to include the skill table.
gorm.DB.Preload("Skills")
.Joins("INNER JOIN job_skill js ON js.job_id = jobs.id").
.Joins("INNER JOIN skill s ON s.id = js.skill_id").
.Where("s.name = ?", s).Find(&jobs)

Related

Group by column and scan into custom struct

I have the following model. Each instance is part of a group which is only defined as the string GroupName here because the actual group is defined in a different service using a different database.
type Instance struct {
gorm.Model
UserID uint
Name string `gorm:"index:idx_name_and_group,unique"`
GroupName string `gorm:"index:idx_name_and_group,unique"`
StackName string
DeployLog string `gorm:"type:text"`
Preset bool
PresetID uint
}
I'd like to scan, the above model, into the following struct. Thus grouping instances why their group name.
type GroupWithInstances struct {
Name string
Instances []*model.Instance
}
I'm been trying my luck with the following gorm code
var result []GroupWithInstances
err := r.db.
Model(&model.Instance{}).
Where("group_name IN ?", names).
Where("preset = ?", presets).
Group("group_name").
Scan(&result).Error
indent, _ := json.MarshalIndent(result, "", " ")
log.Println(string(indent))
But I'm getting the following error
ERROR: column "instances.id" must appear in the GROUP BY clause or be used in an aggregate function (SQLSTATE 42803)
I'm not sure how to deal with that since I don't want to group by instances but rather their groups.
The error indicates that your RDBMS is in Full Group By Mode - cannot select field that isn't in group by clause or used in an aggregate function (SUM, AVG,...). There are 2 solutions:
Disable Full Group By mode. Example in MySQL
Modify the query
Even when we go with Solution 1, gorm will throw another error about relationship between GroupWithInstances and Instance.
So I think we should review the feature and go with Solution 1 - only select what is needed.

How to Preload and Select only records that match the condition specified in the Preload clause

I have been beating my head against this for a while now, but haven't found a reliable solution yet.
In GORM, if I have two tables
type Person struct {
Accounts []Account // hasMany relationship. Single person has many accounts
}
type Account struct {
Name string
PersonID string
}
How do I retrieve a tag that has a matching name?
I am expecting to retrieve a matching Person with given account name.
the closest I have gotten to is with
// DB is a gorm.DB struct
DB.Preload("Accounts", "name = ?", "foo").Find(&personList)
However, this does conditional preloading (duh) and not search on the joined table for a matching account name.

Querying Many to Many with Conditions

I have a gorm many2many model like this:
type User struct {
gorm.Model
Username string
LikedBooks []Books `gorm:"many2many:user_liked_books;"`
}
type Book struct {
gorm.Model
Name string
Likes []User `gorm:"many2many:user_liked_books;"`
}
Where a User can like many Books and a Book can have many Users that like it.
I now want to query for Books that have been liked, and return the top 50 liked books.
How can I achieve that using gorm? I dont understand how to query with conditions on book.liked = true, sorted by liked count, limited by 50.
I couldnt find an example like that in the docs or on stackoverflow.
This could be done in the same way you would construct a SQL query for the request you described, just using the gorm functions. It could look something like this:
var books []Book
tx := db.Table("books").
Joins("INNER JOIN user_liked_books ulb ON ulb.book_id = books.id").
Select("books.id, books.name, count(ulb.user_id) as likes_count").
Group("books.id, books.name").
Order("likes_count desc").
Limit(50).
Find(&books)
If you would also want to load the Likes field, try adding .Preload("Likes") to the construct above.

"Creation At" time in GORM Customise Join table

I am trying to customize many2many table join. I have two tables from which I want to have taken the ids and want another field, which will tell me when the entry in the join table was made. The ids are coming fine, but the "created_at" is not updating and shows "Null" instead of time.
// this is the table join struct which I want to make
type UserChallenges struct {
gorm.JoinTableHandler
CreatedAt time.Time
UserID int
ChallengeID int
}
//hook before create
func (UserChallenges) BeforeCreate(Db \*gorm.DB) error {
Db.SetJoinTableHandler(&User{}, "ChallengeId", &UserChallenges{})
return nil
}
This is not giving any error on the build. Please tell me what I am missing so that I can get the creation time field in this.
PS - The documentation of GORM on gorm.io is still showing SetupJoinTable method but it is deprecated in the newer version. There is a SetJoinTableHandler but there is no documentation available for it anywhere.
The thing to get about using a Join Table model is that if you want to access fields inside the model, you must query it explicitly.
That is using db.Model(&User{ID: 1}).Association("Challenges").Find(&challenges) or db.Preload("Challenges").Find(&users), etc. will just give you collections of the associated struct and in those there is no place in which to put the extra fields!
For that you would do:
joins := []UserChallenges{}
db.Where("user_id = ?", user.ID).Find(&joins)
// now joins contains all the records in the join table pertaining to user.ID,
// you can access joins[i].CreatedAt for example.
If you wanted also to retrieve the Challenges with that, you could modify your join struct to integrate the BelongsTo relation that it has with Challenge and preload it:
type UserChallenges struct {
UserID int `gorm:"primaryKey"`
ChallengeID int `gorm:"primaryKey"`
Challenge Challenge
CreatedAt time.Time
}
joins := []UserChallenges{}
db.Where("user_id = ?", user.ID).Joins("Challenge").Find(&joins)
// now joins[i].Challenge is populated

Linq Contains issue: cannot formulate the equivalent of 'WHERE IN' query

In the table ReservationWorkerPeriods there are records of all workers that are planned to work on a given period on any possible machine.
The additional table WorkerOnMachineOnConstructionSite contains columns workerId, MachineId and ConstructionSiteId.
From the table ReservationWorkerPeriods I would like to retrieve just workers who work on selected machine.
In order to retrieve just relevant records from WorkerOnMachineOnConstructionSite table I have written the following code:
var relevantWorkerOnMachineOnConstructionSite = (from cswm in currentConstructionSiteSchedule.ContrustionSiteWorkerOnMachine
where cswm.MachineId == machineId
select cswm).ToList();
workerOnMachineOnConstructionSite = relevantWorkerOnMachineOnConstructionSite as List<ContrustionSiteWorkerOnMachine>;
These records are also used in the application so I don't want to bypass the above code even if is possible to directly retrieve just workerPeriods for workers who work on selected machine. Anyway I haven't figured out how it is possible to retrieve the relevant workerPeriods once we know which userIDs are relevant.
I have tried the following code:
var userIDs = from w in workerOnMachineOnConstructionSite select new {w.WorkerId};
List<ReservationWorkerPeriods> workerPeriods = currentConstructionSiteSchedule.ReservationWorkerPeriods.ToList();
allocatedWorkers = workerPeriods.Where(wp => userIDs.Contains(wp.WorkerId));
but it seems to be incorrect and don't know how to fix it. Does anyone know what is the problem and how it is possible to retrieve just records which contain userIDs from the list?
Currently, you are constructing an anonymous object on the fly, with one property. You'll want to grab the id directly with (note the missing curly braces):
var userIDs = from w in workerOnMachineOnConstructionSite select w.WorkerId;
Also, in such cases, don't call ToList on it - the variable userIDs just contains the query, not the result. If you use that variable in a further query, the provider can translate it to a single sql query.

Resources