Golang 中的跨数据库准备好的语句绑定(like 和 where in)

Posted

技术标签:

【中文标题】Golang 中的跨数据库准备好的语句绑定(like 和 where in)【英文标题】:Cross-database prepared statement binding (like and where in) in Golang 【发布时间】:2015-02-01 23:25:57 【问题描述】:

看了很多教程,我发现Go中有很多方法可以在prepared statement上绑定参数,其中一些

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-statement 的参数?

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

第三个问题,也没有给出IN语句的例子,如何正确绑定IN语句的参数?

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

【问题讨论】:

第一个问题:不确定,但我认为没有跨数据库的方式。它 100% 取决于您使用的数据库驱动程序。第二个/第三个问题:我再次认为这取决于数据库驱动程序,但我在 postgres 中使用过它,您在这里使用的格式与您在 where 中使用的格式相同(在我使用的驱动程序中,它是 $1、$2 等...)。 【参考方案1】:

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

使用数据库/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() 

    // 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)
 

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

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

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

您有几种解决问题的方法:

您可以在 IN 子句中使用固定数量的占位符构建查询。仅绑定您提供的参数,并通过 NULL 值完成其他占位符。如果您有比您选择的固定数量更多的值,只需执行几次查询。这不是很优雅,但很有效。

您可以使用不同数量的占位符构建多个查询。 IN (?) 的一个查询,IN (?, ?) 的第二个查询,IN (?,?,?) 的第三个查询,等等......将这些准备好的查询保存在语句缓存中,并在运行时间取决于输入参数的数量。注意它占用内存,而且一般prepared statements的最大数量是有限的,所以在参数数量较多的情况下不能使用。

如果输入参数的数量很大,则将它们插入到临时表中,并通过与临时表的连接来替换带有 IN 子句的查询。如果您设法在一次往返中执行临时表中的插入,则它是有效的。用Go和database/sql,不方便,因为没有办法批量查询。

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

【讨论】:

准备好的语句是否缓存在 Golang 中?我读到它们可以提高效率,但似乎没有人将它们作为常量存储在任何地方。【参考方案2】:

我是 Go 新手,但只是回答第一部分:

第一个问题,绑定参数的跨数据库方式是什么? (适用于任何数据库)

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

【讨论】:

【参考方案3】:

我也有同样的问题,阅读答案后开始寻找其他解决方案,了解如何为 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() 

    // 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)

【讨论】:

以上是关于Golang 中的跨数据库准备好的语句绑定(like 和 where in)的主要内容,如果未能解决你的问题,请参考以下文章

mysqli_stmt::bind_result():绑定变量的数量与准备好的语句中的字段数量不匹配

MySQLi准备好的语句不绑定整数

mysqli_stmt_bind_param():变量的数量与绑定参数中准备好的语句中的参数数量不匹配

带有 golang 准备语句的原始 sql 事务

了解 PDO 准备好的语句和绑定参数

单独使用准备好的语句进行占位符绑定是不是过大?