GORM Playground Link
https://github.com/go-gorm/playground/pull/454
Description
Instead of users.age, pets.age is used because the table is derived from Find, and not from the Where clause that receives a struct
Explain your user case and expected results
var pets []*Pet
err := DB.
Joins("INNER JOIN users ON users.id = pets.user_id").
Where(&Pet{Name: "a"}).
Where(&User{Age: 10}).
Find(&pets).Error
if err != nil {
t.Fatalf("err: %v", err)
}
Currently, the generated query is:
SELECT `pets`.`id`,`pets`.`created_at`,`pets`.`updated_at`,`pets`.`deleted_at`,`pets`.`user_id`,`pets`.`name` FROM `pets` INNER JOIN users ON users.id = pets.user_id WHERE `pets`.`name` = "a" AND `pets`.`age` = 10 AND `pets`.`deleted_at` IS NULL
main_test.go:21: err: no such column: pets.age
The expected query is:
SELECT `pets`.`id`,`pets`.`created_at`,`pets`.`updated_at`,`pets`.`deleted_at`,`pets`.`user_id`,`pets`.`name` FROM `pets` INNER JOIN users ON users.id = pets.user_id WHERE `pets`.`name` = "a" AND `users`.`age` = 10 AND `pets`.`deleted_at` IS NULL
Note that in the first query pets.age is used instead of users.age.
In statement.go in:
func (stmt *Statement) BuildCondition(query interface{}, args ...interface{}) []clause.Expression
the test reaches the case reflect.Struct: section.
In it, it reaches conds = append(conds, clause.Eq{Column: clause.Column{Table: clause.CurrentTable, Name: field.DBName}, Value: v}).
Later on, clause.CurrentTable is converted to pets because of the Find clause.
Note that in the exact same place, field.Schema.Table is equal to users. i.e. if we switch:
conds = append(conds, clause.Eq{Column: clause.Column{Table: clause.CurrentTable, Name: field.DBName}, Value: v})to:
conds = append(conds, clause.Eq{Column: clause.Column{Table: field.Schema.Table, Name: field.DBName}, Value: v})
it starts to work fine.
Same apply for other append(conds... statements in this function.
Comment From: a631807682
This seems like an unmanageable problem.
type User struct {
gorm.Model
...
FavoritePet *Pet
NastyPet *Pet
...
}
When we use Where(&Pet{Name: "a"}), we can't determine whether the query is FavoritePet or NastyPet.
Comment From: samyonr
This seems like an unmanageable problem.
type User struct { gorm.Model ... FavoritePet *Pet NastyPet *Pet ... }When we use
Where(&Pet{Name: "a"}), we can't determine whether the query isFavoritePetorNastyPet.
In any of these cases, the query Where(&Pet{Name: "a"}) should output:
`pets`.`name` = "a"
Which it does and it works fine.
The problem is with the second Where clause: Where(&User{Age: 10}) which is translated into:
`pets`.`age` = 10
(the table pet has no age column, hence the main_test.go:21: err: no such column: pets.age error).
Instead, it should be translated into
`users`.`age` = 10
It worked fine for me when I changed Table: clause.CurrentTable to Table: field.Schema.Table in
statement.go->BuildCondition
Comment From: jinzhu
Suggest to write it like:
db.Joins("Company", DB.Where(&Company{Alive: true})).Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id` AND `Company`.`alive` = true;
Refer: https://gorm.io/docs/query.html#Joins-Preloading
Where accepts any structs to build the condition, and it is not necessary to use current model, so you can write query like:
db.Where(Search{Name: "jinzhu"}).Find(&users)
Use its table name to build the SQL will break this.
Comment From: mnussbaum
Suggest to write it like:
db.Joins("Company", DB.Where(&Company{Alive: true})).Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id` AND `Company`.`alive` = true;
@jinzhu in the above example, what is DB? Is it a gorm type that isn't an instantiated DB? Or is it the same as the lowercase db?
Comment From: jjkoh95
Hi @jinzhu
db.Joins("Company", db.Where(&Company{Alive: true})).Find(&users)
select
`users`.`id`,
`users`.`name`,
`users`.`age`,
`Company`.`id` as `Company__id`,
`Company`.`name` as `Company__name`
from
`users`
left join `companies` as `Company` on
`users`.`company_id` = `Company`.`id`
and `Company`.`alive` = true;
This query still returns me rows that don't fulfill the condition (when Company.alive is not true, the related model is returned as empty instead)
I'm able to achieve using this
db.Joins("Company").Where(`"Company"."alive" = ?`, true).Find(&users)
select
`users`.`id`,
`users`.`name`,
`users`.`age`,
`Company`.`id` as `Company__id`,
`Company`.`name` as `Company__name`
from
`users`
left join `companies` as `Company` on
`users`.`company_id` = `Company`.`id`
where
`Company`.`alive` = true;
While this is not a problem for most the times, there is a little consistency in the primary table (snake_case) and the joined table (PascalCase) which makes it slightly less elegant when i try to construct my where conditions, can you share what is the way to alias them? (eg, making the Joins table alias into snake_case instead). Or simply use TableName instead.