Let's say I have these struct that represent my models
type QuestionHeader struct {
QuestionHeaderId int `gorm:"primaryKey;column:question_id" json:"question_id"`
LevelId int `gorm:"column:level_id" json:"level_id"`
SubjectId int `gorm:"column:subject_id" json:"subject_id"`
QuestionBody []QuestionBody
QuestionSolution []QuestionSolution
OtherColumn1A string
OtherColumn2A string
}
type QuestionBody struct {
QuestionBody int `gorm:"primaryKey;column:question_id" json:"question_id"`
Value string `gorm:"column:value" json:"value"`
Type string `gorm:"column:type" json:"type"`
OtherColumn1B string
OtherColumn2B string
}
type QuestionSolution struct {
QuestionSolutionId int `gorm:"primaryKey;column:question_id" json:"question_id"`
Value string `gorm:"column:value" json:"value"`
Type string `gorm:"column:type" json:"type"`
OtherColumn1C string
OtherColumn2c string
}
Then I want to do a query with preload like this
qs.db().
Preload("QuestionHeader.QuestionBody", func(db *gorm.DB) *gorm.DB {
return db.Where("question_body.status = ?", 1)
// I only want to select certain column here
}).
Preload("QuestionHeader.QuestionSolution", func(db *gorm.DB) *gorm.DB {
return db.Where("question_solution.status = ?", 1)
// I only want to select certain column here
}).
Where("level_id = ?", 2).
Find(&questionHeaders) // I only want to select certain column here
But the catch with that code is, I will select all the columns based on my model property
What if I want to select only a certain column with preload?
For example, I want to select only type for QuestionBody
I know that you can create another struct for your select statement as described in the docs, but are there any other ways? I don't want to create a struct for every select statement
In Laravel Eloquent, you can do something like this
There are few issue that i would like to point out
You don't have the many2many association defined in QuestionHeader for QuestionBody or QuestionSolution
In query you are using status column but I don't seem them defined in QuestionBody or QuestionSolution
Rename QuestionBody id column to QuestionBodyId in QuestionBody
As per the points changes would be as below:
type QuestionHeader struct {
QuestionHeaderId int `gorm:"primaryKey;column:question_id" json:"question_id"`
LevelId int `gorm:"column:level_id" json:"level_id"`
SubjectId int `gorm:"column:subject_id" json:"subject_id"`
QuestionBody []QuestionBody `gorm:"many2many:question_header_question_bodies;"`
QuestionSolution []QuestionSolution `gorm:"many2many:question_header_question_solutions;"`
OtherColumn1A string
OtherColumn2A string
}
type QuestionBody struct {
QuestionBodyId int `gorm:"primaryKey;column:question_id" json:"question_id"`
Value string `gorm:"column:value" json:"value"`
Type string `gorm:"column:type" json:"type"`
Status uint8 `gorm:"column:status;default:0" json:"status"`
OtherColumn1B string
OtherColumn2B string
}
type QuestionSolution struct {
QuestionSolutionId int `gorm:"primaryKey;column:question_id" json:"question_id"`
Value string `gorm:"column:value" json:"value"`
Type string `gorm:"column:type" json:"type"`
Status uint8 `gorm:"column:status;default:0" json:"status"`
OtherColumn1C string
OtherColumn2c string
}
var questionHeaders QuestionHeader
db.
Preload("QuestionBody", func(db *gorm.DB) *gorm.DB {
return db.Where("Status = ?", 1).Select("QuestionBodyId", "Value")
}).
Preload("QuestionSolution", func(db *gorm.DB) *gorm.DB {
return db.Where("Status = ?", 1).Select("QuestionSolutionId", "Value")
}).
Where("level_id = ?", 2).
Select("SubjectId").
Find(&questionHeaders)
}
In Preload Select we would have to include the unique primary key for gorm to uniquely identify the associated slice of structs
Related
I am trying to get extra columns from a many2many relationships on Gorm. Example
Part
type Part struct {
Id unit
Name string
}
Diagram
type Diagram struct {
Id unit
Name string
Parts []Part `gorm:"many2many:diagram_parts;"`
}
DiagramPart
type DiagramPart struct{
DiagramId uint `gorm:"primaryKey"`
PartId uint `gorm:"primaryKey"`
PartDiagramNumber int
PartNumber string
PartDescription string
}
This is what I have done trying to retrieve PartNumber and PartDescription in Parts.
diagram := &Diagram{}
db := s.db.Where("id = ?", 1).
Preload("Parts", func(db *gorm.DB) *gorm.DB {
return db.Select("parts.*, diagram_parts.part_number, diagram_parts.part_description").
Joins("left join diagram_parts on diagram_parts.part_id = parts.id")
}).
First(diagram)
Unfortunately, I am not able to retrieve part_number, part_description. How should I go about it?
You can add field PartNumber and PartDescription on struct Part OR Diagram, then add tag gorm:"-:migration;->" on than fields to ignore migration and to readonly mode. But on your situation, you can add it in struct Part because you already preload it.
source: https://gorm.io/docs/models.html#Field-Level-Permission
here's the example:
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Part struct {
Id uint `gorm:"primaryKey"`
Name string
PartNumber string `gorm:"-:migration;->"`
PartDescription string `gorm:"-:migration;->"`
}
type Diagram struct {
Id uint `gorm:"primaryKey"`
Name string
Parts []Part `gorm:"many2many:diagram_parts;"`
// PartNumber string `gorm:"-:migration;->"`
// PartDescription string `gorm:"-:migration;->"`
}
type DiagramPart struct {
DiagramId uint `gorm:"primaryKey"`
PartId uint `gorm:"primaryKey"`
PartDiagramNumber int
PartNumber string
PartDescription string
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&Diagram{}, &Part{}, &DiagramPart{})
diagram := &Diagram{}
err = db.Debug().Where("id = ?", 1).
// Select("diagrams.*, diagram_parts.part_number, diagram_parts.part_description").
Preload("Parts", func(db *gorm.DB) *gorm.DB {
return db.Select("parts.*, diagram_parts.part_number, diagram_parts.part_description").
Joins("left join diagram_parts on diagram_parts.part_id = parts.id")
}).
// Joins("left join diagram_parts on diagram_parts.diagram_id = diagrams.id").
First(diagram).Error
if err != nil {
panic(err)
}
fmt.Printf("diagram: %v\n", diagram)
fmt.Println("part number:", diagram.Parts[0].PartNumber)
}
Here is the struct in models_gen.go
type Students struct {
StudentID string `json:"student_id"`
Class string `json:"class"`
TestsHistory []*TestsHistory `json:"test_history"
}
type TestsHistory struct {
English string `json:"eng"`
Math string `json:"math"`
Music string `json:"music"`
PE string `json:"pe"`
}
The query function in resolvers.go:
func (r *queryResolver) GetStudents(ctx context.Context, id *int) (*model.Students, error) {
var students []*model.students
engine.Sync2(new(model.students))
fmt.Printf("%v\n", engine.Find(students))
return nil, nil
}
When I check MySQL, the students table only contains "StudentID" and "Class" Field, no "TestsHistory". How do I get "TestsHistory" in resolvers?
This query here below works, where the where clause works becuase it's using values from ResourceUsage struct, but I would love to be able to do something like this, where the where clause is using value from the ResourceMetadata struct.
db.Preload("ResourceMetadata").Where("resource_type = ?", resourceType).Where("timestamp BETWEEN ? AND ?", start, end).Limit(10).Find(&resourceUsage)
But it throws exception:
2019-12-03 11:06:12] Error 1054: Unknown column 'resource_type' in 'where clause'
Code:
// ResourceUsage describes the storage model for resource-usage
type ResourceUsage struct {
ID int64 `json:"-"`
DetailsID int64 `json:"details_id"`
ResourceMetadata *ResourceMetadata `gorm:"foreignkey:DetailsID;association_foreignkey:ID"`
MeasuredType string `json:"measured_type"`
Quantity float64 `json:"quantity"`
Timestamp int64 `json:"timestamp"`
}
// ResourceMetadata describes the storage model for resource-usage
type ResourceMetadata struct {
ID int64 `json:"-"`
ResourceUUID string `json:"resource_uuid"`
ResourceName string `json:"resource_name"`
ResourceDisplayName string `json:"resource_display_name"`
ResourceType string `json:"resource_type"`
}
db.Preload("ResourceMetadata").Where("timestamp BETWEEN ? AND ?", start, end).Limit(10).Find(&resourceUsage)
Can you use a JOIN?
resourceUsages := []ResourceUsage{}
if err := db.Joins("JOIN resource_metadata ON resource_metadata.id=details_id").
Where("resource_metadata.resource_type = ? AND timestamp BETWEEN ? AND ?", resourceType, start, end).
Preload("ResourceMetadata").Limit(10).
Find(&resourceUsages).Error; err != nil {
// ... handle error ...
}
I'm in the process of cleaning some code up and am trying to pass a slice value which is a struct to a function.
My struct looks like this:
type GetRecipesPaginatedResponse struct {
Total int `json:"total"`
PerPage int `json:"per_page"`
CurrentPage int `json:"current_page"`
LastPage int `json:"last_page"`
NextPageURL string `json:"next_page_url"`
PrevPageURL interface{} `json:"prev_page_url"`
From int `json:"from"`
To int `json:"to"`
Data []struct {
ID int `json:"id"`
ParentRecipeID int `json:"parent_recipe_id"`
UserID int `json:"user_id"`
Name string `json:"name"`
Description string `json:"description"`
IsActive bool `json:"is_active"`
IsPublic bool `json:"is_public"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
BjcpStyle struct {
SubCategoryID string `json:"sub_category_id"`
CategoryName string `json:"category_name"`
SubCategoryName string `json:"sub_category_name"`
} `json:"bjcp_style"`
UnitType struct {
ID int `json:"id"`
Name string `json:"name"`
} `json:"unit_type"`
} `json:"data"`
}
In my code, I fetch some JSON data from an API and get a response back that contains about 100 items in the Data slice.
I am then looping over each item in the Data slice and processing them.
As an example, it looks something like this:
for page := 1; page < 3; page++ {
newRecipes := getFromRecipesEndpoint(page, latestTimeStamp) //this returns an instance of GetRecipesPaginatedResponse
for _, newRecipe := range newRecipes.Data {
if rowExists(db, "SELECT id from community_recipes WHERE id=#id", sql.Named("id", newRecipe.ID)) {
insertIntoRecipes(db, true, newRecipe)
} else {
insertIntoRecipes(db, false, newRecipe)
}
}
}
So I am trying to pass the instance of a recipe to the insertIntoRecipes function, which looks like this:
func insertIntoRecipes(db *sql.DB, exists bool, newRecipe GetRecipesPaginatedResponse.Data) {
if exists {
//update the existing record in the DB
//perform some other updates with the information
} else {
//insert a new record into the DB
//insert some other information using this information
}
}
When I run this, I get the error:
GetRecipesPaginatedResponse.Data undefined (type GetRecipesPaginatedResponse has no method Data)
I can tell that the issue is to do with how I am trying to pass the newRecipe to the insertIntoRecipes function, newRecipe GetRecipesPaginatedResponse.Data, however I am not quite sure how to pass it in and declare the right variable type.
To pass an item inside the Data slice to a function, when I am looping over each item of the Data slice, how do I do that?
You cannot reference the anonymous type of the Data field using a field selector. The fix is to declare a named type for the Data field:
type GetRecipesPaginatedResponse struct {
...
Data []DataItem
...
}
type DataItem struct {
ID int `json:"id"`
ParentRecipeID int `json:"parent_recipe_id"`
UserID int `json:"user_id"`
Name string `json:"name"`
...
}
Use it like this:
func insertIntoRecipes(db *sql.DB, exists bool, newRecipe DataItem) {
...
}
There's probably a better name for DataItem.
i have my model struct like the below :
type Detail struct {
Product
Stocks
}
type Product struct {
Name string `db:"name"`
Id int `db:"id"`
}
type Stocks {
Name string `db:"name"`
Price float `db:"price"`
Type string `db:"type"`
}
i would have a query to join the above tables like the below :
query, args, err := sqlx.In("select p.name , s.price from Product p,Stocks s where p.name=s.name and type IN (?)",typecodes)
query = s.Cmd.Db.Rebind(query)
var rows *sqlx.Rows
rows, err = s.Cmd.Db.Queryx(query, args...)
for rows.Next() {
var p model.Detail
err = rows.StructScan(&p)
}
Would like to know when i do rows.StructScan(&p) will the Product structure Name field be populated or will there be any ambuigity found for the same since Stocks also have a Name field ?
Currently i am not getting any result for the above.But when i comment the Name field in the Stocks struct, i am getting the data.
Let me know what i am missing here.
For ambiguous fields you're best annotating them with a prefix of their struct name, e.g. product_name, stock_name, then alias them appropriately in your SQL statement.
I.e.
type Detail struct {
Product
Stocks
}
type Product struct {
Name string `db:"product_name"`
Id int `db:"id"`
}
type Stocks {
Name string `db:"stock_name"`
Price float `db:"price"`
Type string `db:"type"`
}
And in your SQL:
SELECT p.name AS product_name, s.name AS stock_name, ... FROM Product p, Stocks s WHERE ...