How to insert with select using GORM? - mysql

Is there a way to insert(create) rows with select in a single query using GORM?
What I am trying to do is to join two tables and insert rows into another table using a selected value from the table (using insert/select) but I'm having a hard time finding a way to call create along with select using GORM.
Basically what I hope to do can be done in a below SQL query:
INSERT INTO table_two (val, name, age)
SELECT table_one.some_value, '', 0
FROM table_one
WHERE table_one.some_value = 50
This inserts new rows into table_two with val column values set to the some_value of each of the matched rows in table_one.
Thanks in advance.

I was able to implement your request in two ways.
The first approach is to use a raw SQL Query (the same that you provide in your question). Thanks to the db.Exec() function you're able to run a raw SQL statement against your MySQL instance.
The second approach is to split the operation into two parts:
First you read the data with Where and Find
Second you insert the data with Create
Below, you can find both solutions:
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type TableOne struct {
SomeValue int
Name string
Age int
}
type TableTwo struct {
Val int
Name string
Age int
}
func main() {
// refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details
dsn := "root:root#tcp(127.0.0.1:3306)/todo?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// first approach - Raw SQL
if dbTrn := db.Exec(`INSERT INTO table_twos (val, name, age)
SELECT table_ones.some_value, '', 0
FROM table_ones
WHERE table_ones.some_value = 50`).Error; dbTrn != nil {
panic(dbTrn.Error)
}
// second approach - split into two actions
var recordsOne []TableOne
recordsTwo := make([]TableTwo, 0)
db.Where("some_value=?", 50).Find(&recordsOne)
for _, v := range recordsOne {
recordsTwo = append(recordsTwo, TableTwo{Val: v.SomeValue, Name: "", Age: 0})
}
db.Create(&recordsTwo)
}
Let me know if this works also for you.

Related

How to create mysql database record dynamically

How would it be possible to create a database record (in this case mysql) without literally specifying each variable to be passed in to the model, on or at the model (see following example). With having to create dozens of models, I am trying to avoid having to specify these vars manually with each and every model. I can dynamically collect this list on the controller and am hoping there is some way to just pass one object from the controller to the model (in this failed experiment: res []string).
// controller
_, err := policy.CreateItem(c.DB, res)
// model
func CreateItem(db Connection, res []string) (sql.Result, error) {
// res = ParentID, Title, Description, Sort, Date, StatusID, UserID
tfcim := format.TitlesForCreateInputModel(ItemMin{}) // parent_id, title, description, sort, date, status_id, user_id
phfcm := format.PlaceHoldersForCreateModel(ItemMin{}) // ?,?,?,?,?,?,?
result, err := db.Exec(fmt.Sprintf(`
INSERT INTO %v
(`+tfcim+`)
VALUES
(`+phfcm+`)
`, table),
strings.Join(res, ", ")) // <----------------- How can magic happen here?
return result, err
}
And logically, the output is: flight.go:138: sql: expected 7 arguments, got 1
To note, ItemMin{} is the table struct returning the table keys using 'reflect' in a package named format.
Any functional method, even an entirely different approach (except using an ORM/GORM) would be acceptable. Thanks in advance!
UPDATE: Complete working solution:
// controller
s := make([]interface{}, len(res))
for i, v := range res {
s[i] = v
}
_, err := policy.CreateItem(c.DB, s)
// model
func CreateItem(db Connection, res []interface{}) (sql.Result, error) {
tfcim := format.TitlesForCreateInputModel(ItemMin{}) // parent_id, title, description, sort, date, status_id, user_id
phfcm := format.PlaceHoldersForCreateModel(ItemMin{}) // ?,?,?,?,?,?,?
query := fmt.Sprintf("INSERT INTO %v (%v) VALUES (%v)", table, tfcim, phfcm)
result, err := db.Exec(query, res...)
return result, err
}
As the Exec function has this signature:
Exec(query string, args ...interface{})
You can use this syntax:
query := fmt.Sprintf("INSERT INTO %v (%v) VALUES (%v)", table, tfcim, phfcm)
result, err := db.Exec(query, res...)

How to get RowsAffected in Go-Gorm's Association Mode

I insert a relation by the following code:
db.Where(exercise).FirstOrCreate(&exercise).Model(&User{ID: userID}).Association("Exercises").Append(&exercise)
Corresponding SQL printed by debug console to the code is:
INSERT INTO `user_exercise` (`user_id`,`exercise_id`) SELECT 1,1 FROM DUAL WHERE NOT EXISTS (SELECT * FROM `user_exercise` WHERE `user_id` = 1 AND `exercise_id` = 1)
I want know if there are new record created in user_exercise, but due to the generated SQL, if a relation in user_exercise already exists, it won't insert and produce no error.
Go-Gorm's Association object doesn't have a RowsAffected attribute, so I can't get RowsAffected from the query to confirm if a new record is created.
Though I can get RowsAffected from the first db object, like
db.Where(exercise).FirstOrCreate(&exercise).Model(&User{ID: userID}).Association("Exercises").Append(&exercise)
if db.RowsAffected == 1 {
// do something
}
I wonder since the db is shared by all queries, if another query executed at the same time and affected rows > 0, is it safe to get RowsAffected from the global db object?
Assuming that the user_execise table has an unique constraint (user_id, exercise_id) the insert should return an error if you try to do it to an already created record. (Exactly what you want)
So just do something like this...
db.Where(exercise).FirstOrCreate(&exercise)
ue := struct {
UserID uint
ExerciseID uint
}{
UserID: userID,
ExerciseID exercise.ID
}
if err := db.Table("user_exercise").Create(&ue).Error; err != nil {
// will enter here if it wasn't created
}
If it doesn't returns an Error means that a new record was created

How do I stop GORM from sorting my preload by ID?

In my database I have a users table, joined via a many-to-many table to schools. A school has many jobs. I'm trying to return all the schools and their jobs for a specific user. This is my code so far:
var user User
err := db.Where("id = ?", userID).Preload("Schools")
.Preload("Schools.Jobs", func(db *gorm.DB) *gorm.DB {
return db.Order("job.job_reference DESC")
}).First(&user).Error
return &user.Schools, err
Gorm is then executing the following queries:
SELECT * FROM `user` WHERE (id = 'foo') ORDER BY `user`.`id` ASC LIMIT 1
SELECT * FROM `school`
INNER JOIN `user_school` ON `user_school`.`school_id` = `school`.`id`
WHERE (`user_school`.`user_id` IN ('foo'))
SELECT * FROM `job` WHERE (`school_id` IN ('1','2'))
ORDER BY job.job_reference DESC,`job`.`id` ASC
The first two generated SQL queries are exectly what I expected however the last query tries to sort by the criteria I provided AND a default sort by ID. If I remove my specific sort instructions it still tries to sort by ID. How can I stop it from doing that?
I had the same problem. When you use .First() it needs to be ordered by something. Just change in your example to use Scan instead:
err := db.Where("id = ?", userID).Preload("Schools")
.Preload("Schools.Jobs", func(db *gorm.DB) *gorm.DB {
return db.Order("job.job_reference DESC")
}).Scan(&user).Error
return &user.Schools, err
source: https://github.com/go-gorm/gorm/blob/master/finisher_api.go#L113

SQL + goLang - running select statement with an IN condition - and passing it an array of ids?

I want to perform the following logic -
SELECT some_column
FROM table_name
WHERE id IN (value1, value2, ...);
in my go code I have the following int array to represent my ids :
idsToGet := []int{1,2}
I fully understand that you might normally do a sprintf and pass in the one id you were looking for... BUT...
I feel like there is a sensible way to do this without iterating over the array, and doing this as a separate call for each ID... via sprintf - is there a way I can do this as just one call - and have my IN clause auto contain everything from my array?
Totally new to Go - been trying to solve this for an hour, I have it working for single id's - but not for multiples.
You don't want to use Sprintf to put raw values into SQL, that's a bad habit. What you want to do is build a bit of SQL with the right number of placeholders and then let the library take care of replacing the placeholders with their value.
If you have two ints in your array then you want to build this:
SELECT some_column
FROM table_name
WHERE id IN (?, ?)
If you have four then you want to build:
SELECT some_column
FROM table_name
WHERE id IN (?, ?, ?, ?)
and so on. All you need is a simple function that can produce n placeholders; there are lots of ways to do this:
func placeholders(n int) string {
ps := make([]string, n)
for i := 0; i < n; i++ {
ps[i] = "?"
}
return strings.Join(ps, ",")
}
or maybe:
func placeholders(n int) string {
var b strings.Builder
for i := 0; i < n - 1; i++ {
b.WriteString("?,")
}
if n > 0 {
b.WriteString("?")
}
return b.String()
}
or whatever is clearest to you, the implementation is unlikely to have any measurable impact as long as it is correct.
Then you can say things like:
query := fmt.Sprintf("select some_column from table_name where id in (%s)", placeholders(len(idsToGet)))
rows, err := db.Query(query, idsToGet...)
and spin throw rows in the usual manner.

Why go-sql-driver fails to handle NULL in MySQL bigint field?

I am using go-sql-driver to connect to MySQL database. In one of my table I am using a field called queue_length as BIGINT. This field is NULL by default. When I try to query all the fields from the table using go-sql-driver in Golang, the fields after queue_length are not coming in the result set including queue_length.
In my use case,
Table fields
[unique_id, qid, title, text, queue_length, user_id, date_created, last_updated]
When I execute the following code I am getting values for fields before queue_length but [queue_length, user_id, date_created, last_updated] fields are not coming in result set.
But if I don't select queue_length, all fields are coming in result set. I am scanning queue_length in queueLength variable in code which is of type int64. The value coming from table is NULL. I tried type conversion also. Didn't work.
Is this a bug in go-sql-driver or am I doing anything wrong? Could anyone help me with this?
func GetQueues(uniqueId string) ([]*objects.Queue, error) {
db, err := GetDBConnection()
defer db.Close()
rows, err := db.Query("SELECT qid, title, text, queue_length, user_id, date_created, last_updated FROM queue WHERE unique_id = ?", uniqueId)
if err != nil {
panic(err.Error())
}
defer rows.Close()
var qId string
var title string
var text string
var userId string
var dateCreated string
var lastUpdated string
var queueLength int64
var queue *objects.Queue
var queues []*objects.Queue
for rows.Next() {
err = rows.Scan(&qId, &title, &text, &queueLength, &userId, &dateCreated, &lastUpdated)
queue = &objects.Queue{ QId: qId, Title: title, Text: text, UniqueId: uniqueId, UserId: userId, DateCreated: dateCreated, LastUpdated: lastUpdated, QueueLength: queueLength }
queues = append(queues, queue)
}
err = rows.Err()
if err != nil {
panic(err.Error())
return nil, err
}
return queues, err
}
queueLength should be a NullInt64 instead of an int64.
var queueLength sql.NullInt64
In Go ints can't be nil so there's no way for Scan to save NULL in queueLength.
I use this tool for Null and Zero fields, it includes many data types.
The github url is https://github.com/guregu/null