在Golang中进行跨数据库的预处理语句绑定(例如like和where in)

huangapple go评论77阅读模式
英文:

Cross-database prepared statement binding (like and where in) in Golang

问题

阅读了许多教程后,我发现在Go语言中有多种方法可以绑定预处理语句中的参数,其中一些方法如下:

SELECT * FROM bla WHERE x = ?col1 AND y = ?col2
SELECT * FROM bla WHERE x = ? AND y = ?
SELECT * FROM bla WHERE x = :col1 AND y = :col2
SELECT * FROM bla WHERE x = $1 AND y = $2

第一个问题,有没有一种跨数据库的方法可以绑定参数?(适用于任何数据库)

第二个问题,我阅读的教程中没有提到LIKE语句,如何正确地绑定LIKE语句的参数?

SELECT * FROM bla WHERE x LIKE /*什么?*/

第三个问题,这些教程中也没有给出IN语句的示例,如何正确地绑定IN语句的参数?

SELECT * FROM bla WHERE x IN ( /*什么?*/ )
英文:

After reading many tutorials, I found that there are many ways to bind arguments on prepared statement in Go, some of them

SELECT * FROM bla WHERE x = ?col1 AND y = ?col2
SELECT * FROM bla WHERE x = ? AND y = ?
SELECT * FROM bla WHERE x = :col1 AND y = :col2
SELECT * FROM bla WHERE x = $1 AND y = $2

First question, what is the cross-database way to bind arguments? (that works on any database)

Second question, none of the tutorial I've read mention about LIKE statement, how to bind arguments for LIKE-statement correctly?

SELECT * FROM bla WHERE x LIKE /*WHAT?*/

Third question, also none of them give an example for IN statement, how to bind arguments for IN statement correctly?

`SELECT * FROM bla WHERE x IN ( /*WHAT?*/ )

答案1

得分: 10

跨数据库绑定参数的方法是什么?

使用database/sql,没有通用的方法。每个数据库都有自己表示参数占位符的方式。Go的database/sql包没有提供任何规范化预处理语句的功能。预处理语句文本只是传递给底层驱动程序,驱动程序通常会将其原样发送到数据库服务器(或嵌入式数据库的库)。

如何正确地为LIKE语句绑定参数?

您可以在LIKE语句后使用参数占位符,并将其绑定为字符串。例如,您可以编写一个预处理语句:

SELECT a from bla WHERE b LIKE ?

这是一个示例(省略了错误处理)。

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

// > select * from bla ;
// +------+------+
// | a    | b    |
// +------+------+
// | toto | titi |
// | bobo | bibi |
// +------+------+


func main() {

    // 打开连接
    db, err := sql.Open("mysql", "root:XXXXXXX@/test")
    if err != nil {
         panic(err.Error())  // 在您的应用程序中使用适当的错误处理,而不是 panic
    }
    defer db.Close()
 
    // 准备用于读取数据的语句
    stmtOut, err := db.Prepare("SELECT a FROM bla WHERE b LIKE ?")
    if err != nil {
        panic(err.Error()) // 在您的应用程序中使用适当的错误处理,而不是 panic
    }
    defer stmtOut.Close()

    var a string
    b := "bi%"    // LIKE 'bi%'
    err = stmtOut.QueryRow(b).Scan(&a)
    if err != nil {
        panic(err.Error()) // 在您的应用程序中使用适当的错误处理,而不是 panic
    }
    fmt.Printf("a = %s\n", a)
} 

注意,%字符是绑定字符串的一部分,而不是查询文本的一部分。

如何正确地为IN语句绑定参数?

我所知道的数据库都不允许直接使用IN子句绑定参数列表。这不是database/sql或驱动程序的限制,而是大多数数据库服务器不支持的。

您有几种方法可以解决这个问题:

  • 您可以构建一个具有固定数量占位符的查询,将您提供的参数绑定到占位符,并使用NULL值填充其他占位符。如果您有比您选择的固定数量更多的值,只需多次执行查询即可。这不是非常优雅,但可以有效。

  • 您可以构建多个具有不同数量占位符的查询。一个查询用于IN ( ? ),第二个查询用于IN (?, ?),第三个查询用于IN (?,?,?)等等...将这些预处理查询保存在语句缓存中,并根据输入参数的数量在运行时选择正确的查询。请注意,这需要内存,并且通常预处理语句的最大数量是有限的,因此在参数数量较大时无法使用。

  • 如果输入参数的数量很大,请将它们插入临时表,并将查询中的IN子句替换为与临时表的连接。如果您能够在一次往返中执行插入到临时表的操作,这种方法是有效的。但是,由于Go和database/sql没有批量查询的方式,因此在Go中使用这种方法并不方便。

这些解决方案都有缺点,没有一个是完美的。

英文:

What is the cross-database way to bind arguments?

With database/sql, there is none. Each database has its own way to represent parameter placeholders. The Go database/sql package does not provide any normalization facility for the prepared statements. Prepared statement texts are just passed to the underlying driver, and the driver typically just sends them unmodified to the database server (or library for embedded databases).

How to bind arguments for LIKE-statement correctly?

You can use parameter placeholders after a like statement and bind it as a string. For instance, you could write a prepared statement as:

SELECT a from bla WHERE b LIKE ?

Here is an example (error management handling omitted).

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

// > select * from bla ;
// +------+------+
// | a    | b    |
// +------+------+
// | toto | titi |
// | bobo | bibi |
// +------+------+

func main() {

    // Open connection
    db, err := sql.Open("mysql", "root:XXXXXXX@/test")
    if err != nil {
         panic(err.Error())  // proper error handling instead of panic in your app
    }
    defer db.Close()
 
    // Prepare statement for reading data
    stmtOut, err := db.Prepare("SELECT a FROM bla WHERE b LIKE ?")
    if err != nil {
        panic(err.Error()) // proper error handling instead of panic in your app
    }
    defer stmtOut.Close()

    var a string
    b := "bi%"    // LIKE 'bi%'
    err = stmtOut.QueryRow(b).Scan(&a)
    if err != nil {
        panic(err.Error()) // proper error handling instead of panic in your app
    }
    fmt.Printf("a = %s\n", a)
} 

Note that the % character is part of the bound string, not of the query text.

How to bind arguments for IN statement correctly?

None of the databases I know allows binding a list of parameters directly with a IN clause. This is not a limitation of database/sql or the drivers, but this is simply not supported by most database servers.

You have several ways to work the problem around:

  • you can build a query with a fixed number of placeholders in the IN clause. Only bind the parameters you are provided with, and complete the other placeholders by the NULL value. If you have more values than the fixed number you have chosen, just execute the query several times. This is not extremely elegant, but it can be effective.

  • you can build multiple queries with various number of placeholders. One query for IN ( ? ), a second query for IN (?, ?), a third for IN (?,?,?), etc ... Keep those prepared queries in a statement cache, and choose the right one at runtime depending on the number of input parameters. Note that it takes memory, and generally the maximum number of prepared statements is limited, so it cannot be used when the number of parameters is high.

  • if the number of input parameters is high, insert them in a temporary table, and replace the query with the IN clause by a join with the temporary table. It is effective if you manage to perform the insertion in the temporary table in one roundtrip. With Go and database/sql, it is not convenient because there is no way to batch queries.

Each of these solutions has drawbacks. None of them is perfect.

答案2

得分: 2

我是一个Go的新手,只回答第一个问题:

第一个问题,有没有一种跨数据库的方式来绑定参数?(适用于任何数据库)

如果你使用 sqlx,它是内置的sql包的超集,那么你应该可以使用 sqlx.DB.Rebind 来实现这个功能。

英文:

I'm a newbie to Go but just to answer the first part:
> First question, what is the cross-database way to bind arguments? (that works on any database)

If you use sqlx, which is a superset of the built-in sql package, then you should be able to use sqlx.DB.Rebind to achieve that.

答案3

得分: 2

我有同样的问题,并在阅读答案后开始寻找其他解决方案来绑定IN语句的参数。

以下是我所做的示例,虽然不是最优雅的解决方案,但对我来说有效。

我所做的是创建一个带有静态设置参数的查询语句,而不使用绑定功能。

可能需要对来自Marshal命令的字符串进行清理,以确保安全,但我现在不需要。

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

type Result struct {
	Identifier string
	Enabled    bool
}

func main() {

	// 打开数据库连接
	db, err := sql.Open("mysql", "username:password@tcp(server-host)/my-database")
	if err != nil {
		panic(err.Error()) // 在你的应用程序中使用适当的错误处理,而不是 panic
	}
	defer db.Close()

	// 这是一个可变数量的 ID 列表示例
	idList := []string{"ID1", "ID2", "ID3", "ID4", "ID5", "IDx"}

	// 将列表转换为 JSON 字符串
	formatted, _ := json.Marshal(idList)

	// JSON 数组以 '[]' 开始和结束,所以我们将它们替换为 '()'
	formatted[0] = '('
	formatted[len(formatted)-1] = ')'

	// 创建一个静态的 select 查询
	query := fmt.Sprintf("SELECT identifier, is_enabled FROM some_table WHERE identifier in %s", string(formatted))

	// 准备查询
	rows, err := db.Query(query)
	if err != nil {
		panic(err.Error()) // 在你的应用程序中使用适当的错误处理,而不是 panic
	}
	defer rows.Close()

	var result []Result
	// 获取行数据
	for rows.Next() {
		var r0 Result
		if err := rows.Scan(&r0.Identifier, &r0.Enabled); err != nil {
			log.Fatal(err)
		}
		// 将行数据添加到结果中
		result = append(result, r0)
	}
	if err := rows.Err(); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("result = %v\n", result)
}

希望对你有所帮助!

英文:

I had this same question, and after reading the answers started to look for other solution on how to bind arguments for the IN statement.

Here is an example of what I did, not the most elegant solution, but works for me.

What I did was to create a select query with the parameters statically set on the query, and not using the bind feature at all.

It could be a good idea to sanitize the string that comes from the Marshal command, to be sure and safe, but I don't need it now.

package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
type Result struct {
Identifier string
Enabled    bool
}
func main() {
// Open connection
db, err := sql.Open("mysql", "username:password@tcp(server-host)/my-database")
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
defer db.Close()
// this is an example of a variable list of IDs
idList := []string{"ID1", "ID2", "ID3", "ID4", "ID5", "IDx"}
// convert the list to a JSON string
formatted, _ := json.Marshal(idList)
// a JSON array starts and ends with '[]' respectivelly, so we replace them with '()'
formatted[0] = '('
formatted[len(formatted)-1] = ')'
// create a static select query
query := fmt.Sprintf("SELECT identifier, is_enabled FROM some_table WHERE identifier in %s", string(formatted))
// prepare que query
rows, err := db.Query(query)
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
defer rows.Close()
var result []Result
// fetch rows
for rows.Next() {
var r0 Result
if err := rows.Scan(&r0.Identifier, &r0.Enabled); err != nil {
log.Fatal(err)
}
// append the row to the result
result = append(result, r0)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
fmt.Printf("result = %v\n", result)
}

huangapple
  • 本文由 发表于 2014年12月4日 14:54:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/27287990.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定