I have products table which has one-to-many relationship with items and brands tables. brands also have one-to-many relationship with items.
I was trying to query products and group them based on product_id and brand_id and it seems to work fine.
The only problem I have right now is that I can't map these fields brandId and BrandName, They always nil. But querying the raw SQL statement it generates the desired result.
Here are my models. Some fields have been omitted for simplicity.
type Brand struct {
ID int `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"index;not null;type:varchar(50);default:null"`
ProductID int `json:"productId"`
Product *Product `json:"product" gorm:"foreignKey:ProductID;"`
}
type Product struct {
ID int `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"index;not null;type:varchar(50);default:null"`
StoreID *int `json:"storeId"`
Store *Store `json:"store" gorm:"foreignKey:StoreID;constraint:OnUpdate:RESTRICT,OnDelete:RESTRICT;"`
BrandID *int `json:"brandId" gorm:"-"` //SEE THIS
BrandName *string `json:"brandName" gorm:"-"` //SEE THIS TOO
Brands []*Brand `json:"brands" gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;"`
}
type Item struct {
ID int `json:"id" gorm:"primaryKey"`
Quantity int `json:"quantity" gorm:"type:integer;not null;unsigned;"`
ProductID int `json:"productId"`
Product *Product `json:"product" gorm:"foreignKey:ProductID;not null;constraint:OnUpdate:RESTRICT,OnDelete:CASCADE;"`
BrandID *int `json:"brandId"`
Brand *Brand `json:"brand" gorm:"foreignKey:BrandID;constraint:OnUpdate:RESTRICT,OnDelete:CASCADE;"`
}
var products []*model.Product
var result *gorm.DB
query := DB.Table("products").
Where(&model.Product{StoreID: &StoreID}).
Joins("INNER JOIN items ON items.product_id = products.id").
Joins("LEFT JOIN brands ON brands.id = items.brand_id").
Where("items.quantity > 0").
Group("products.id, brands.id").
Select("products.*,brands.id AS brand_id, brands.name AS brand_name")
if err := query.Find(&products).Error; err != nil {
panic(err)
}
//Here results contains no brand_id nor brand_name
fmt.Printf("result %+v\n", products)
Here is raw SQL generated by gorm. And work as expected outside gorm
SELECT products.*,brands.id AS brand_id, brands.name AS brand_name
FROM "products"
INNER JOIN items ON items.product_id = products.id
LEFT JOIN brands ON brands.id = items.brand_id
WHERE "products"."store_id" = 2 AND items.quantity > 0
GROUP BY products.id, brands.id
First solution that comes in mind is to use -> instead of - gorm tag.
Like this
BrandID : Int #goTag(key: "gorm", value: "->")
BrandName : String #goTag(key: "gorm", value: "->")
The only drawback with this is that it saves these fields on database.
Any alternative?
has_many
type Brand struct {
ID int `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"index;not null;type:varchar(50);default:null"`
ProductID int `json:"productId"`
}
type Product struct {
ID int `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"index;not null;type:varchar(50);default:null"`
StoreID *int `json:"storeId"`
Store *Store `json:"store" gorm:"foreignKey:StoreID;constraint:OnUpdate:RESTRICT,OnDelete:RESTRICT;"`
Brands []*Brand `json:"brands" gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;"`
}
you shouold read BrandId and BrandName in model-Brand but model-Product
Product have many Brands, so it have many BrandID/BrandName, why you defiene just a BrandNameor BrandID?
Related
I have two models: Order and OrderItem. I need to insert order and item of it from same request, e.g.:
{
"user_id": "1",
"total_price": "200",
"items": [
{
"product_id": 1,
"quantity": 10
},
{
"product_id": 2,
"quantity": 5
},
{
"product_id": 3,
"quantity":3
}
]
}
This is Order and OrderItem model
=========================== Order ===========================
type Order struct {
ID int `boil:"id" json:"id" toml:"id" yaml:"id"`
OrderNumber string `boil:"order_number" json:"order_number" toml:"order_number" yaml:"order_number"`
OrderDate time.Time `boil:"order_date" json:"order_date" toml:"order_date" yaml:"order_date"`
Status string `boil:"status" json:"status" toml:"status" yaml:"status"`
Note string `boil:"note" json:"note" toml:"note" yaml:"note"`
UserID int `boil:"user_id" json:"user_id" toml:"user_id" yaml:"user_id"`
CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"`
UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"`
R *orderR `boil:"-" json:"-" toml:"-" yaml:"-"`
L orderL `boil:"-" json:"-" toml:"-" yaml:"-"`
}
=========================== OrderItem ===========================
type OrderItem struct {
ID int `boil:"id" json:"id" toml:"id" yaml:"id"`
OrderID int `boil:"order_id" json:"order_id" toml:"order_id" yaml:"order_id"`
ProductID int `boil:"product_id" json:"product_id" toml:"product_id" yaml:"product_id"`
ProductPrice float64 `boil:"product_price" json:"product_price" toml:"product_price" yaml:"product_price"`
ProductName string `boil:"product_name" json:"product_name" toml:"product_name" yaml:"product_name"`
Quantity int `boil:"quantity" json:"quantity" toml:"quantity" yaml:"quantity"`
Discount float64 `boil:"discount" json:"discount" toml:"discount" yaml:"discount"`
Note string `boil:"note" json:"note" toml:"note" yaml:"note"`
CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"`
UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"`
R *orderItemR `boil:"-" json:"-" toml:"-" yaml:"-"`
L orderItemL `boil:"-" json:"-" toml:"-" yaml:"-"`
}
What do people usually do? Is there a way to do this quickly with sqlboiler?
I think you needn't to do that, you can insert order model and orderItems model in different requests. This solution provide more option than for client. If you need to update or add new order item into the order, you also need this API to solve it.
Create some models like
type OrderItemInput struct {
ProductId int `json:"product_id"`
Quantity int `json:"quantity"`
}
type OrderInsertInput struct {
UserID int `json:"user_id"`
TotalPrice float64 `json:"total_price"`
Items []OrderItemInput `json:"items"`
}
Create new Order by fields UserId and TotalPrice of OrderInsertInput.
When there was OrderID, we will create OrderItem with each OrderItemInput and the OrderID.
Struct sample
type Car struct {
ID uint64
Required bool `pg:"required,notnull"`
Name string `pg:"name"`
}
Migration:
BEGIN;
ALTER TABLE cars ADD COLUMN required BOOLEAN NOT NULL DEFAULT true;
END;
When I create car struct:
car = Car{Name:"Name",Required:false}
When i'm trying to add some new car by writing:
_, err = r.db.Model(&car).Insert()
SQL Query looks like this:
INSERT INTO "cars" ("id", "name", "required") VALUES (DEFAULT, "Name", DEFAULT)
The main problem that car has required field set as false, but when I inserting it - it changes to DEFAULT (true).
Because the value false will be read as a null value. Because null value your data will be change to the default value (TRUE)
You must change the struct to like this
type Car struct {
ID uint64
Required *bool `pg:"required,notnull,default:true"`
Name string `pg:"name"`
}
and define struct like this
required := false
car = Car{Name:"Name", Required: &required}
or you can also use data type sql.NullBool in your struct
type Car struct {
ID uint64
Required sql.NullBool `pg:"required,notnull,default:true"`
Name string `pg:"name"`
}
car = Car{Name:"Name", Required: sql.NullBool{Bool: false, Valid: true}}
I have the following entities:
type User struct {
ID string
Name string
Groups []Groups `gorm:"many2many:users_groups"`
}
type Groups struct {
ID string
Name string
}
I know I can preload the groups using
var users []Users
db.Preload("Groups").Find(&users)
And I can also filter Users using
var users []Users
db.Preload("Groups").Where("name IN ?", []string{"name1", "name2"}).Find(&users)
This will bring all the User that have name equals "name1" or "name2"
But I cannot filter Users based on Groups
var users []Users
db.Preload("Groups", "name IN ?", []string{"groupname"}).Find(&users)
I expect it to bring all Users that have Group Name equal "groupname"
How can I achieve this using database only? (my database is big and I cannot load all users into memory and filter it in the application)
I opened an issue on gorm repository as well
You may fetch users by group names very easily:
db.Preload("Groups").Where("id IN (SELECT user_id FROM users_groups WHERE name IN ?)", []string{"name1", "name2"})
Or, a more strict way:
db.Where("id IN (?)", db.Table("users_groups").
Select("user_id").
Where("name IN ?", []string{"name1", "name2"}),
)
I had the same problem and I solve it with using Join.
Probably it's late for your problem but this could help folks when they have the same problem.
solution for your case would be :
var users []Users
db.Preload("Groups").
Joins("inner join user_groups ug on ug.user_id = user.id ").
Joins("inner join groups g on g.id= ug.user_id ").
Where("g.name IN ?", []string{"name1", "name2"}).Find(&users)
ref: https://gorm.io/docs/query.html
you can use:
db.Preload("Groups", "name IN (?)", []string{"name1", "name2"}).Find(&users)
(edited) this is also just filter the Groups, not the users (by there groups)
Actually I did came up with a solution of querying a gorm model containing 3-4 many 2 many relationships with filters for main model as well as filters for fields inside association.
Here filters type is map[string]string
noOfAssociationFilters stores how many associations we have found as for the first association our col name will be id (field in Lawyer Model) and for other nested queries it will be lawyer_id (field name in associations).
var lawyers []models.Lawyer
var noOfAssociationFilters int = 0
var statement string = "SELECT * FROM lawyers WHERE 1=1"
if filters["experience"] != "" {
statement += " AND `experience` = " + filters["experience"]
}
if filters["location_id"] != "" {
statement += " AND `location_id` = " + filters["location_id"]
}
if filters["gender"] != "" {
statement += " AND `gender` = \"" + filters["gender"] + "\""
}
if filters["practice_area_id"] != "" {
colName := func() string {
if noOfAssociationFilters == 0 {
return " `id`"
} else {
return " `lawyer_id`"
}
}()
statement += " AND" + colName + " IN ( SELECT `lawyer_id` FROM lawyer_practice_areas WHERE practice_area_id = " + filters["practice_area_id"]
noOfAssociationFilters += 1
}
if filters["court_id"] != "" {
colName := func() string {
if noOfAssociationFilters == 0 {
return "`id`"
} else {
return "`lawyer_id`"
}
}()
statement += " AND" + colName + " IN ( SELECT `lawyer_id` FROM lawyer_courts WHERE court_id = " + filters["court_id"]
noOfAssociationFilters += 1
}
if filters["language_id"] != "" {
colName := func() string {
if noOfAssociationFilters == 0 {
return "`id`"
} else {
return "`lawyer_id`"
}
}()
statement += " AND" + colName + " IN ( SELECT `lawyer_id` FROM lawyer_languages WHERE language_id = " + filters["language_id"]
noOfAssociationFilters += 1
}
for i := 0; i < noOfAssociationFilters; i++ {
statement += ")"
}
statement += ";"
fmt.Println(statement)
result := db.Raw(statement).Find(&lawyers)
fmt.Println("RowsAffected", result.RowsAffected)
I have tested the solution and it was working fine for me. It generates the complete statement you need in the end based upon your filter requirements. If you pass a filter then it generates a statement for that.
It generates statements like these:-
SELECT * FROM lawyers WHERE 1=1 AND experience = 5 AND location_id = 4 AND gender = "female" AND id IN ( SELECT lawyer_id FROM lawyer_practice_areas WHERE practice_area_id = 3 ANDlawyer_id IN ( SELECT lawyer_id FROM lawyer_courts WHERE court_id = 3));
SELECT * FROM lawyers WHERE 1=1 AND experience = 5 AND location_id = 4 AND gender = "female";
By this logic we can generate nested queries and get required data for m2m relations.
For reference this was my Lawyer Model
type Lawyer struct {
ID uint `gorm:"primaryKey" json:"id"`
FirstName string `gorm:"type:varchar(100) not null" json:"first_name"`
LastName string `gorm:"type:varchar(100) not null" json:"last_name"`
FullName string `gorm:"->;type:text GENERATED ALWAYS AS (concat(first_name,' ',last_name)) VIRTUAL;" json:"full_name"`
LocationID uint `gorm:"not null" json:"location_id"`
Location Location `gorm:"foreignKey:location_id" json:"location"`
Email string `gorm:"unique;not null" json:"email"`
Phone string `gorm:"type:varchar(100);not null" json:"phone"`
Password string `gorm:"type:varchar(100);not null" json:"password"`
ImageURL string `gorm:"type:text" json:"image_url"`
Education string `gorm:"not null" json:"education"`
Experience uint `gorm:"not null" json:"experience"`
Verified bool `gorm:"not null" json:"verified"`
Gender string `gorm:"not null" json:"gender"`
Reviews []Review `gorm:"foreignKey:LawyerID" json:"reviews"`
Courts []Court `gorm:"many2many:lawyer_courts" json:"courts"`
Languages []Language `gorm:"many2many:lawyer_languages" json:"languages"`
PracticeAreas []LawyerPracticeArea `gorm:"foreignKey:LawyerID" json:"practice_areas"` }
And one of the many2many association was of this type
type LawyerPracticeArea struct {
ID uint `gorm:"primaryKey" json:"id"`
LawyerID uint `gorm:"not null" json:"lawyer_id"`
Lawyer Lawyer `gorm:"foreignKey:LawyerID" json:"lawyer"`
PracticeAreaID uint `gorm:"not null" json:"practice_area_id"`
PracticeArea PracticeArea `gorm:"foreignKey:PracticeAreaID" json:"practice_area"`
Charge int `gorm:"" json:"charge"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` }
Upvote if you found this helpful :) !!
I want to use gorm the use a many to one relationship in my project.
My structs are like this:
type Book struct {
ID uint
Title string
Subtitle string
Chapters []Chapter `gorm:"foreignkey:BookID;association_foreignkey:ID"`
}
}
// TableName is book
func (Book) TableName() string {
return "book"
}
// Chapter of books
type Chapter struct {
ID uint
BookID string
Chapter string
}
What I want is to get chapters of a book by using this command:book.chapters.
I use the following codes to get books and chapters:
var book models.Book
db.First(&book, "id = ?", 4)
fmt.Println(book.ID, book.Chapters) // returns []
when I query chapters with book_id = 4, I get 11 results:
var chapters []models.Chapter
db.Find(&chapters, "book_id = ? ", 4)
fmt.Println("len(chapters) = ", len(chapters)) // len(chapters) =11
and when I set db.LogMode(true) and see what happens in the code I see that querying book only queries the books not joining that with chapters:
SELECT * FROM "book" WHERE (id = 4) ORDER BY "book"."id" ASC LIMIT 1
Is there something that I missed from the documentation? how should I make the book struct the get non-empty book.chapters.
Your problem is probably in Preloading: http://gorm.io/docs/preload.html#Preload
Try the following:
var book models.Book
db.Preload("Chapters").First(&book, "id = ?", 4)
fmt.Println(book.ID, book.Chapters)
I'm trying to create an Angular7 + graphql + sqlite game app and i'm stuck.
I've 3 db tables, one for games, other for genres and a third one as a relational table between games and genres.
What I want to do on graphql is basically this join:
SELECT G.*, GN.genre FROM Games AS G
LEFT JOIN Game_Genre AS GG ON G.id = GG.game_id
LEFT JOIN Genre AS GN ON GG.genre_id = GN.id
WHERE G.id={GAMEID}
expected result
I managed to create queries to list all games and individual games, but I can't make any queries to list the games with their genres.
Here's the schema I'm using:
const schema = buildSchema(`
type Query {
games(offset:Int = 0, limit:Int = 100): [Games]
game(id:ID!): Games
}
type Games {
id: ID
game_art: String
game_title: String
game_serial: String
game_region: String
release_year: Int
players: Int
developer: String
publisher: String
}
type Genre {
id: ID
genre: String
}
type Game_Genre {
id: Int
game_id: Games
genre_id: Genre
}`);
const root = {
games: args => {
return query(
`SELECT * FROM Games LIMIT ${args.offset}, ${args.limit}`,
false
);
},
game: args => {
return query(`SELECT * FROM Games WHERE id='${args.id}'`, true);
}};
Thank you in advance