One to many inserting - go

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.

Related

GORM: Select fields from two tables while joining

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?

GO pg prevent default value

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}}

Convert string in struct to []string

I have a struct as below:
type TourData struct {
ArtistID int //artist ID
RelationID string //key for relations
City string
Country string
TourDates []string
}
type MyRelation struct {
ID int `json:"id"`
DatesLocations map[string][]string `json:"datesLocations"`
}
which contains this data from a csv file:
1,nagoya-japan,Nagoya,Japan,
1,penrose-new_zealand,Penrose,New_Zealand,
1,dunedin-new_zealand,Dunedin,New_Zealand,
2,playa_del_carmen-mexico,Playa Del Carmen,Mexico,
2,papeete-french_polynesia,Papeete,French_Polynesia,
MyRelations is populated from an API which contains:
"index": [
{
"id": 1,
"datesLocations": {
"dunedin-new_zealand": [
"10-02-2020"
],
"nagoya-japan": [
"30-01-2019"
],
"penrose-new_zealand": [
"07-02-2020"
]
}
},
{
"id": 2,
"datesLocations": {
"papeete-french_polynesia": [
"16-11-2019"
],
"playa_del_carmen-mexico": [
"05-12-2019",
"06-12-2019",
"07-12-2019",
"08-12-2019",
"09-12-2019"
]
}
}
The dates come from another struct. The code I have used to populate this struct is as below:
var oneRecord TourData
var allRecords []TourData
for _, each := range csvData {
oneRecord.ArtistID, _ = strconv.Atoi(each[0])
oneRecord.RelationID = each[1]
oneRecord.City = each[2]
oneRecord.Country = each[3]
oneRecord.TourDates = Relations.Index[oneRecord.ArtistID-1].DatesLocations[each[1]]
allRecords = append(allRecords, oneRecord)
}
jsondata, err := json.Marshal(allRecords) // convert to JSON
json.Unmarshal(jsondata, &TourThings)
I need to group all the 1s together then the 2s etc. I thought to create another struct, and populate from this one but not having much luck - any ideas?
To clarify I would want say TourData.City to equal:
[Nagoya,Penrose,Dunedin]
[Playa Del Carmen, Papeete]
At the moment if I was to print TourData[0].City I would get Nagoya.
I have tried creating another struct to be populated from the TourData struct with the following fields:
type TourDataArrays struct {
ArtistID int
City []string
Country []string
TourDates [][]string
}
and then populate the struct using the code below:
var tourRecord TourDataArrays
var tourRecords []TourDataArrays
for i := 0; i < len(Relations.Index); i++ {
for j := 0; j < len(allRecords); j++ {
if allRecords[i].ArtistID == i+1 {
tourRecord.City = append(tourRecord.City, allRecords[j].City)
}
}
tourRecords = append(tourRecords, tourRecord)
}
However this is adding all the cities to one array i.e
[Nagoya, Penrose, Dunedin, Playa Del Carmen, Papeete].
If I understand your requirements correctly you needed to declare city as a string array as well. (And Country to go with it).
Check out this solution : https://go.dev/play/p/osgkbfWV3c5
Note I have not deduped country and derived city and country from one field in the Json.

How to query a many2many relationship with a Where clause on the association with go-gorm?

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 :) !!

How to match two Lists with only items that are different in Linq

I have a StudentData class
public class StudentData
{
public int Id { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
public int? Bonus { get; set; }
public int? Subject1Mark { get; set; }
public int? Subject2Mark { get; set; }
public int? Subject3Mark{ get; set; }
}
Each student has a unique Id that identifies him
I have a List<StudentData> CurrentData that has data of
1, John, Smith, 10, 50 ,50 ,50
2, Peter, Parker, 10, 60 ,60 ,60
3, Sally, Smart, 10, 70 ,70 ,70
4, Danny, Darko, 20, 80, 80, 80
I then have a List<StudentData> DataToUpdate which only contains the Id and Marks fields. Not the other fields.
1, null, null, null, 50 ,50 ,50
2, null, null, null, 65, 60 ,60
3, null, null, null, 70 ,70 ,70
The Ids of the list are not necessary in the same order
If you compare the two lists only Peter Parker's marks have changed in one subject.
I want to get the output to return
2, Peter, Parker, 10, 65 ,60 ,60
I want to takeList<StudentData> CurrentData inner join this with List<StudentData> DataToUpdate but only where marks are different
So in SQL it want the following
SELECT
CurrentData.Id,
CurrentData.FirstName ,
CurrentData.Surname,
CurrentData.Bonus,
DataToUpdate.Subject1Mark,
DataToUpdate.Subject2Mark,
DataToUpdate.Subject3Mark
FROM CurrentData
INNER JOIN DataToUpdate
ON CurrentData.Id= DataToUpdate.Id
AND (
CurrentData.Subject1Mark<> DataToUpdate.Subject1Mark
OR
CurrentData.Subject2Mark<> DataToUpdate.Subject2Mark
OR
CurrentData.Subject3Mark<> DataToUpdate.Subject3Mark
)
How do I do the above in LINQ?
In the Linq select how do I take all properties from CurrentData but include the 3 Subject properties from DataToUpdate in it to give me List<ChangedData>?
I could map each and every property but my StudentData has 100 fields and I would prefer to have something like
select new StudentData {
this=CurrentData,
this.Subject1Mark=DataToUpdate.Subject1Mark,
this.Subject2Mark=DataToUpdate.Subject2Mark,
this.Subject3Mark=DataToUpdate.Subject3Mark,
}
but I'm not sure how to write this
There is an answer in another stackoverflow question which should work but it doesn't. If I implement that solution (I simplify the example for simplicity)
var changedData = currentData
.Join(dataToUpdate, cd => cd.Id, ld => ld.Id, (cd, ld) => new { cd, ld })
.Select(x => { x.cd.Subject1Mark= x.ld.Subject1Mark; return x.cd; })
;
but the above x.cd.Subject1Mark isn't updated by x.ld.Subject1Mark although I use the answer in the linked stackoverflow question
The structure of LINQ query looks very similar to SQL:
var res =
from cur in CurrentData
join upd in DataToUpdate on upd.Id equals cur.Id
where (cur.Subject1Mark != upd.Subject1Mark || cur.Subject2Mark != upd.Subject2Mark || cur.Subject3Mark != upd.Subject3Mark)
select new {
Current = cur
, UpdatedSubject1Mark = upd.Subject1Mark
, UpdatedSubject2Mark = upd.Subject2Mark
, UpdatedSubject3Mark = upd.Subject3Mark
};
The main difference is that filtering out by inequality has moved from the on clause in SQL to a where clause of LINQ.

Resources