In Postgres in Go, how can I make query parameters optional?
In this example status is an optional condition. If no status is passed all rows from table records will be fetched.
How to make query parameter &d.Status an optional
type QueryParams struct {
Status string `json:"status"`
}
func (r repo) GetRecords(d *QueryParams) ([]*Records, error) {
statusQuery := ""
if d.Status != "" {
statusQuery = " where status = $1 "
}
query := "select id, title, status from records " + statusQuery
rows, err := r.db.Query(query, &d.Status)
}
Query is variadic so you could build an []interface{} to hold the arguments:
args := []interface{}{}
and then to conditionally build the argument list:
if d.Status != "" {
statusQuery = " where status = $1 "
args = append(args, &d.Status)
}
When you run the query, expand the arguments using ...:
rows, err := r.db.Query(query, args...)
You may use a flexible WHERE clause, e.g.
SELECT id, title, status
FROM records
WHERE status = $1 OR $1 IS NULL;
The logic here is that if you provide a value for $1, it must match the status in order for a record to be returned. Otherwise, if $1 be left out (i.e. is NULL), then all records would be returned.
Note that to make this work from Go with the Postgres driver, you may need to do some extra massaging. I would try, at a first attempt, this:
statusQuery = "where status = $1 or $1::text is null"
query := "select id, title, status from records " + statusQuery
rows, err := r.db.Query(query, &d.Status)
Sometimes the driver can't figure out the type of the bound parameter $1. By explicitly casting it to text, the statement can be made to work.
Related
I have a Snowflake query where I'm trying to update a field on all items where another field is in a list which is submitted to the query as a variable:
UPDATE my_table SET download_enabled = ? WHERE provider_id = ? AND symbol IN (?)
I've tried doing this query using the gosnowflake.Array function like this:
enable := true
provider := 1
query := "UPDATE my_table SET download_enabled = ? WHERE provider_id = ? AND symbol IN (?)"
if _, err := client.db.ExecContext(ctx, query, enable, provider,
gosnowflake.Array(assets)); err != nil {
fmt.Printf("Error: %v", err)
}
However, this code fails with the following error:
002099 (42601): SQL compilation error: Batch size of 1 for bind variable 1 not the same as previous size of 2.
So then, how can I submit a variable representing a list of values to an SQL query?
I found a potential workaround, which is to submit each item in the list as a separate parameter explicitly:
func Delimit(s string, sep string, count uint) string {
return strings.Repeat(s+sep, int(count)-1) + s
}
func doQuery(enable bool, provider int, assets ...string) error {
query := fmt.Sprintf("UPDATE my_table SET download_enabled = ? " +
"WHERE provider_id = ? AND symbol IN (%s)", Delimit("?", ", ", uint(len(assets))))
params := []interface{}{enable, provider}
for _, asset := range assets {
params = append(params, asset)
}
if _, err := client.db.ExecContext(ctx, query, params...); err != nil {
return err
}
return nil
}
Needless to say this is a less elegant solution then what I wanted but it does work.
I need to change rows in my DB using 2 arrays, first stores names of rows which i need to change, second stores val. I've added the code , to understand what I want to do. Can i do it with 1 request to my DB
func update_1(){
key := []string{"Name1", "Name2", "Name4"}
val := []string{"1", "2", "4"}
for i, _ := range key{
_, err := db.Exec("UPDATE table SET val = $1 WHERE name = $2",val[i], key[i])
if err != nil {
errorLog.Println(err)
return
}
}
}
You can pass the arrays into a Postgres query as parameters. Then it is a simple unnest() and update:
update t
set val = u.val
from unnest(:ar_names, :ar_vals) u(name, val)
where t.name = u.name;
I am trying to append text to a database column using golang and the pq driver.
The error I am getting is panic: pq: could not determine data type of parameter $2
sqlStatement := `
UPDATE sales
SET description = concat(description, $2)
WHERE id = $1;`
id:=1
desc := "appendthis"
res, err := db.Exec(sqlStatement, id, desc)
if err != nil {
panic(err)
}
I also tried
SET description = description || $2 which didn't panic, though the field did not get updated.
Any ideas what I am doing wrong?
I have sql query that needs variable substitution for better consumption of my go-kit service.
I have dep & org as user inputs which are part of my rest service, for instance: dep = 'abc' and org = 'def'.
I've tried few things like:
rows, err := db.Query(
"select name from table where department='&dep' and organisation='&org'",
)
And:
rows, err := db.Query(
"select name from table where department=? and organisation=?", dep , org,
)
That led to error: sql: statement expects 0 inputs; got 2
Only hard-coded values work and substitution fails .
I haven't found much help from oracle blogs regarding this and wondering if there is any way to approach this.
Parameter Placeholder Syntax (reference: http://go-database-sql.org/prepared.html )
The syntax for placeholder parameters in prepared statements is
database-specific. For example, comparing MySQL, PostgreSQL, and
Oracle:
MySQL PostgreSQL Oracle
===== ========== ======
WHERE col = ? WHERE col = $1 WHERE col = :col
VALUES(?, ?, ?) VALUES($1, $2, $3) VALUES(:val1, :val2, :val3)
For oracle you need to use :dep, :org as placeholders.
As #dakait stated, on your prepare statement you should use : placeholders.
So, for completeness, you would get it working with something like:
package main
import (
"database/sql"
"fmt"
"log"
)
// Output is an example struct
type Output struct {
Name string
}
const (
dep = "abc"
org = "def"
)
func main() {
query := "SELECT name from table WHERE department= :1 and organisation = :2"
q, err := db.Prepare(query)
if err != nil {
log.Fatal(err)
}
defer q.Close()
var out Output
if err := q.QueryRow(dep, org).Scan(&out.Name); err != nil {
log.Fatal(err)
}
fmt.Println(out.Name)
}
I have a query in GO that returns a different response from mysql.
here is the query:
SELECT DISTINCT
questions.id as question_id,
questions.question,
questions.priority,
questions.type
FROM questions
LEFT JOIN profile ON profile.user_id = ?
LEFT JOIN group ON group.user_id = profile.user_id
WHERE questions.status = 1
AND group.status = 1
AND questions.id NOT IN (SELECT DISTINCT question_id FROM answers WHERE user_id = profiles.user_id)
When I run this on mysql terminal it returns nothing as expected. But when I try to run this on GO lang it has a return which is not supposed to be returned since it has already been filtered in the NOT IN clause which are all the answered questions. When I tried to change the profiles.user_id to a specific value it returns the expected output.
I think the use of column parameters in not working in GO. It will be a quick fix if I change the users.profile to a specific variable but there are other queries that needs to use that feature in order to achieve my expected output.
I tried using stmt.Prepared statement and the db.Query() with same results
Go code:
query := " SELECT DISTINCT " +
" questions.id as question_id, " +
" questions.question, " +
" questions.priority, " +
" questions.type " +
" FROM questions " +
" LEFT JOIN profile ON profile.user_id = 1627 " +
" LEFT JOIN group ON group.user_id = profile.user_id " +
" WHERE questions.status = 1 " +
" AND group.status = 1 " +
" AND questions.id NOT IN (SELECT DISTINCT question_id FROM answers WHERE user_id = profiles.user_id); "
stmt, err := db.Prepare(query)
if err != nil {
checkErr(err) // proper error handling instead of panic in your app
}
defer stmt.Close() // Close the statement when we leave main() / the program terminates
userId := json.userId
args := []interface{}{}
args = append(args, userId)
start := time.Now()
rows, err := stmt.Query(args...)
elapsed := time.Since(start)
if err != nil {
checkErr(err) // proper error handling instead of panic in your app
}
// Fetch rows
for rows.Next() {
// get RawBytes from data
err = rows.Scan(&question.QuestionId, &question.Question, &question.Priority, &question.Type)
questions = append(questions, question)
if err != nil {
checkErr(err) // proper error handling instead of panic in your app
}
}
defer rows.Close()
defer db.Close()
Is there any workaround for it to work ?
Thanks for your response
You can use question mark '?' in the query in places where you expect a parameter:
age := 27
rows, err := db.Query("SELECT name FROM users WHERE age=?", age)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
log.Fatal(err)
}
fmt.Printf("%s is %d\n", name, age)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
Also, you can use back tick ` for a multi-line string in your code (rather than concatenation with +). It would make your code easier to read.
query := `SELECT DISTINCT
questions.id as question_id,
questions.question,
questions.priority,
questions.type
FROM questions
LEFT JOIN profile ON profile.user_id = 1627
LEFT JOIN group ON group.user_id = profile.user_id
WHERE questions.status = 1
AND group.status = 1
AND questions.id NOT IN (SELECT DISTINCT question_id FROM answers WHERE user_id = profiles.user_id); `