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...)
Related
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.
I'm trying to update a record in the database, and based on the rowsAffected() count I can decided whether the record exists or not. In which case if it doesn't (rowsAffected() == 0) I will run a insert query.
func (u *UserService) NewAddress(l *models.Address) error {
var err error
db, err := database.GetConnection()
if err != nil {
return err
}
defer db.Close()
sql := `UPDATE Locations SET Address = ?, StateId = ? WHERE Id = ?`
query := `INSERT INTO Locations (UserId, StateId, Address, City, StreetName, StreetNumber, Code, Latitude, Longitude) VALUES(?,?,?,?,?,?,?,?,?)`
stmt, err := db.Prepare(sql)
if err != nil {
return err
}
results, err := stmt.Exec("Port St Johns, South Africa", l.StateId, l.Id)
if err != nil {
return err
}
fmt.Println(results.RowsAffected())
if affected, err := results.RowsAffected(); err == nil {
if affected == 0 {
insert, err := db.Query(query, l.UserId, l.StateId, l.Address, l.City, l.StreetName, l.StreetNumber, l.Code, l.Latitude, l.Longitude)
defer insert.Close()
if err != nil {
return err
}
}
}
return nil
}
However, the RowsAffected() func always returns 0 if the value of the column I'm updating e.i Address is the same as the one already stored in the database. It also returns 0 if it can't find the record I'm trying to update.
If I run the same query directly on MySQL console however, even if the update (address) is exactly the same, it always returns Update Rows >= 1 as long as the WHERE clause condition is met.
UPDATE Locations
SET Address = "Port St Johns", StateId = 2
WHERE Id = 102;
Can I do this update any different? I'm trying to avoid a select query to check if the record exists before updating/inserting.
I added a updated timestamp as #mkopriva suggested. That way there's always an updated count even when all the data is the same, the timestamp will change/update
I am running the following queries in SQL.
SET #thisid=0;
SET #serial=0;
SELECT #serial := IF((#thisid != `places`.`id`), #serial + 1, #serial) as `serial`, #thisid := `places`.`id`, `places`.`id` FROM `places`;
The variable #serial basically increments only if the new id is not the same as the last one.
Upon running these queries in the terminal and printing the values of #serial and #thisid, the value received is #thisid='id6' #serial=6.
I executed this query in my go code:
if _, err = repo.db.ExecContext(ctx, "SET #thisid=0;"); err != nil {
return
}
if _, err = repo.db.ExecContext(ctx, "SET #serial=0;"); err != nil {
return
}
rows, err = repo.db.QueryContext(ctx, fmt.Sprintf(
"SELECT #serial := IF((#thisid != `places`.`id`), #serial + 1, #serial) as `serial`, #thisid := `places`.`id`, `places`.`id` FROM `places`;",
))
if err != nil {
fmt.Println("error here")
return
}
if err = repo.db.QueryRow("SELECT #serial").Scan(&that); err != nil {
return
}
if err = repo.db.QueryRow("SELECT #thisid").Scan(&this); err != nil {
return
}
Upon printing the values of #thisid and #serial, the value of #thisid is received the same where the value of #serial is received as 0. It doesn't seem to update dynamically.
Go uses a connection pool, which means each query may happen on a different connection. variables like that are scoped to the connection. If you need them to last between queries, you need to use a transaction to ensure you stay within the same connection.
Your query is really arbitrary. MySQL does not guarantee the order of evaluation of expressions in a select. Nor does it guarantee the ordering of the result set.
So, I think you want:
select p.*,
(#rn := if(#id = id, #rn + 1,
if(#id := id, 1, 1)
)
) as serial
from (select p.*
from places p
order by p.id
) p cross join
(select #id := 0, #rn := 0) params;
I want to display the number of rows from database using Go. How do I display number of rows?
count, err := db.Query("SELECT COUNT(*) FROM main_table")
The query will return a row into the variable count. So the next you have to do is to read this row and assign the result into a new variable, using the function Scan(). This is how it works.
rows, err := db.Query("SELECT COUNT(*) FROM main_table")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var count int
for rows.Next() {
if err := rows.Scan(&count); err != nil {
log.Fatal(err)
}
}
fmt.Printf("Number of rows are %s\n", count)
The best option thought would be to use QueryRow() as you expect to read just one row. The code then will be.
var count int
err := db.QueryRow("SELECT COUNT(*) FROM main_table").Scan(&count)
switch {
case err != nil:
log.Fatal(err)
default:
fmt.Printf("Number of rows are %s\n", count)
}
I signed up just to share this as my large datasets were slowing down the program with the constant appends. I wanted to put the rowcount in the rows and figured that something like that must exist, just had to find it.
SELECT
count(1) OVER(PARTITION BY domain_id) AS rowcount,
subscription_id,
domain_id,
...
FROM mytable
WHERE domain_id = 2020
Never used this command before, but it will add the count of the result set that share this parameter. Setting it to one of the query WHERE's makes it the total rows.
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