23

I am running the below query, but only get the first id value:-

select * from `table` where table`.`id` in ('1', '2', '3', '4', '5', '6', '7', '9', '11', '13', '14', '15', '17') and `table`.`deleted_at` is null

I have done the following:-

var aID = make([]string, 0)
var in India // india is struct

for rows.Next() {
    cook := rows.Scan(&in.ID)

    aID = append(aID, strconv.Itoa(in.ID))
}

asID = strings.Join(aID, ",")

anotherRow,err := db.Query("SELECT * from table2 where id in (?)", asID)
if err != nil { fmt.Printf("Error: ", err) }
// ... Other line follows up with "for anotherRow.Next() and fetching"

While fetching data, it only returns value of "1" and ignores all other ID passed to it, which are '2', '3', '4', '5', '6', '7', '9', '11', '13', '14', '15', '17'.

How can I pass it correctly?

I am using go-sql-driver/mysql.

FAQ :

  1. aID does contain all those numbers as string and

  2. table has all the rows available with provided above id.

  3. table is from where id is fetched and appended to aID and another record with id stored in aID are fetched with in statement from table2.

Thanks

John Cargo
  • 1,839
  • 2
  • 29
  • 59
  • Are you sure you have all of these ids in table2? – Nebril Jul 27 '17 at 13:13
  • obviously yes, I am able to run mysql query inside mysql cli with all those id and getting return for all. – John Cargo Jul 27 '17 at 13:14
  • You can use `sqlx.In` to solve this. Take a look at https://stackoverflow.com/questions/40565805/how-to-use-sqlx-to-query-mysql-in-a-slice/40567910#40567910 – yazgazan Jul 27 '17 at 13:15
  • Cool, just one thing - your sample sql query uses `table`, query in yoour code uses `table2`. Not sure if relevant. – Nebril Jul 27 '17 at 13:16
  • @Nebril, `table` is from where id is fetched and appended to `aID` and another records with `id` stored in `aID` are fetched with `in` statement from `table2`. – John Cargo Jul 27 '17 at 13:17
  • @yazgazan, No plan to use completely new package for just 1 query. I am using `github.com/go-sql-driver/mysql` – John Cargo Jul 27 '17 at 13:20
  • Are you calling anotherRow.Next() ? You don't show how do you try to access rows. – Nebril Jul 27 '17 at 13:21
  • `github.com/jmoiron/sqlx` is widely used and extremely convenient. It does not replace `github.com/go-sql-driver/mysql` but is a "drop in" replacement for `database/sql`. – yazgazan Jul 27 '17 at 13:22
  • 1
    @JohnCargo your example query shows the ids quoted in single quotes, strconv.Itoa and strings.Join will not result in `'1','2','3',...` but in `1,2,3,...`. – mkopriva Jul 27 '17 at 13:24
  • @mkopriva, yes they are resulting in `1,2,3,..` are they different from `'1', '2', '3' ...` - is there any function which can convert them to `'1', '2'` without writing `for` loop ? – John Cargo Jul 27 '17 at 13:27
  • 1
    @JohnCargo the '?' placeholder gets replaced with the string of numbers separated by commas, so the query results in `WHERE id IN ('1,2,3,4...')` and mysql matches only the first value in that string, so id equal to `1` or `'1'` will match but id equal to `2` or `'2'` will not – mkopriva Jul 27 '17 at 13:43
  • Possible duplicate of [Go and IN clause in Postgres](https://stackoverflow.com/questions/38036752/go-and-in-clause-in-postgres/38037586#38037586). – icza Jul 27 '17 at 13:53
  • @mkopriva , Ok, i see what it does now. Is there any way to format as per requirement without doing `for` loop. – John Cargo Jul 27 '17 at 13:54
  • @icza - this is mysql and it's different. – John Cargo Jul 27 '17 at 13:55
  • @JohnCargo I know, but the first solution in that answer is not postgres specific, so it works with mysql as well. – icza Jul 27 '17 at 13:55
  • you could use sqlx https://stackoverflow.com/questions/40565805/how-to-use-sqlx-to-query-mysql-in-a-slice – Yandry Pozo Jul 28 '17 at 06:30

6 Answers6

39

You can do something like this:

args := make([]interface{}, len(asID))
for i, id := range asID {
    args[i] = id
}
stmt := `SELECT * from table2 where id in (?` + strings.Repeat(",?", len(args)-1) + `)`
anotherRow, err := db.Query(stmt, args...)

Just note you will want to put in a guard if asID can ever have len == 0.

If you have any other arguments to pass in, you'll have to add them to the args slice.

Also to note, you should explicitly name the columns you want so you can guarantee you are scanning in the correct columns to the correct fields.

Gavin
  • 4,365
  • 1
  • 18
  • 27
  • 1
    This is a good solution, because it also prevents sql injection. – Koodimetsa Dec 22 '18 at 18:52
  • I was just about to do something similar but thought "somebody must have done it already", very clever – Jairo Lozano Nov 26 '19 at 05:24
  • Why not `SELECT * from table2 where id in (` + strings.Trim(string(asID), "[]") + `)` – Illud Mar 18 '23 at 23:47
  • 1
    @Illud 1) Because that does not compile (`cannot convert []string{…} (value of type []string) to type string`). 2) Even if that did compile, it seems a bit hacky. 3) Using parameters allows you to avoid SQL injection, while your proposed way would not. – Gavin Mar 20 '23 at 02:51
8

Try

q,args,err := sqlx.In("SELECT * FROM table2 WHERE id IN(?);", asID) //creates the query string and arguments
rows, err := db.Query(q,args...)

You could also use the Masterminds/squirrel package:

import sq "github.com/Masterminds/squirrel"

...

users := sq.Select("*").From("table2")
active := users.Where(sq.Eq{"id":[]string{"1","2","3"}})
sql, args, err := active.ToSql()

Which will do the in clause automatically when using sq.Eq struct with a slice.

Alex Efimov
  • 3,335
  • 1
  • 24
  • 29
1

The most elegant solution for queries to make array/slice work directly with sql queries. This also sql injection proof as you are not using string concatenation rather using sql prepared statement

idAry := []string{"1", "2", "3"}
q := "SELECT * FROM table WHERE id = any($1);"
rows, err := db.Exec(q, pq.Array(authors))
Debasish Mitra
  • 1,394
  • 1
  • 14
  • 17
0

maybe something like this.

func GetPlaceholders(values ...string) (placeholders string, parameters []interface{}) {
    n := len(values)
    p := make([]string, n)
    parameters = make([]interface{}, n)
    for i := 0; i < n; i++ {
        p[i] = "?"
        parameters[i] = values[i]
    }
    placeholders = strings.Join(p, ",")
    return placeholders, parameters
}

and calling the function like this

placeholders, params := GetPlaceholders("1", "2", "3")

rows, err := db.Query(`select language, textkey, text 
        from language where textkey in (`+placeholders+`)
        order by language, textkey`, params...)

Daniel
  • 21
  • 3
-1

Since you're dealing with ids from your own database and if you are certain there is no way someone could inject malicious "ids" into that code, don't use the placeholder ? and just use the fmt package.

fmt.Sprintf("SELECT * from table2 where id in (%s)", asID)

this will result in SELECT * from table2 where id in (1,2,3,4...) as opposed to SELECT * from table2 where id in ('1,2,3,4...')

mkopriva
  • 35,176
  • 4
  • 57
  • 71
  • 4
    Just leaving note for future readers : Though i have selected this answer and this answer is safe for given question. But as the answer above says, the query runs without escaping the arguments , so, this code should not be used where malicious user can inject malicious ID. – John Cargo Jul 30 '17 at 06:28
  • This is how sql injection happens, one genius is writing some code that is 'only' used with strings of known origin and then 1 year later, second genius is using that already written and battle tested function in different context :), usualy after the first genius already left the company. – Tomasz Swider Nov 23 '22 at 17:26
-2

example:

idAry := []string{"1", "2", "3"}
ids := strings.Join(idAry, "','")
sqlRaw := fmt.Sprintf(`SELECT * FROM table WHERE id IN ('%s')`, ids)
rows, err := db.Query(sqlRaw)

It works fine

girish946
  • 745
  • 9
  • 24
Cary
  • 37
  • 3