如何使用包数据库/sql批处理sql语句

如何使用包数据库/sql批处理sql语句,sql,go,Sql,Go,如何使用Go的数据库/sql包批处理sql语句 在Java中,我会这样做: // Create a prepared statement String sql = "INSERT INTO my_table VALUES(?)"; PreparedStatement pstmt = connection.prepareStatement(sql); // Insert 10 rows of data for (int i=0; i<10; i++) { pstmt.setStri

如何使用Go的数据库/sql包批处理sql语句

在Java中,我会这样做:

// Create a prepared statement
String sql = "INSERT INTO my_table VALUES(?)";
PreparedStatement pstmt = connection.prepareStatement(sql);

// Insert 10 rows of data
for (int i=0; i<10; i++) {
    pstmt.setString(1, ""+i);
    pstmt.addBatch();
}

// Execute the batch
int [] updateCounts = pstmt.executeBatch();
//创建一个准备好的语句
字符串sql=“插入到我的表格值(?);
PreparedStatement pstmt=connection.prepareStatement(sql);
//插入10行数据

对于(int i=0;i批处理不可能通过数据库/sql中可用的接口进行。但是,特定的数据库驱动程序可能单独支持它。例如,似乎支持使用MySQL进行批处理。

因为
db.Exec
函数是,一个选项(实际上只进行一次网络往返)就是自己构造语句,分解参数并传递它们

示例代码:

func BulkInsert(unsavedRows[]*ExampleRowStruct)错误{
valueStrings:=make([]字符串,0,len(未保存行))
valueArgs:=make([]接口{},0,len(未保存行)*3)
对于u,post:=范围未保存行{
valueStrings=append(valueStrings,“(?,?,?)”)
valueArgs=append(valueArgs,post.Column1)
valueArgs=append(valueArgs,post.Column2)
valueArgs=append(valueArgs,post.Column3)
}
stmt:=fmt.Sprintf(“插入到我的样本表格(第1列、第2列、第3列)值%s中”,
strings.Join(valueStrings,“,”)
_,err:=db.Exec(stmt,valueArgs…)
返回错误
}

在我运行的一个简单测试中,这个解决方案插入10000行的速度大约是另一个答案中的开始、准备、提交速度的4倍——尽管实际的改进在很大程度上取决于您的个人设置、网络延迟等。

如果您使用PostgreSQL,则支持。

扩展Avi Flax的答案,我需要d我的插入中有一个关于冲突DO UPDATE子句

解决方法是复制到临时表(在事务结束时设置为delete),然后从临时表插入到永久表

以下是我确定的代码:

func(fdata*FDataStore)savetodbulk(项映射[fdataKey][]字节)(错误){
tx,err:=fdata.db.Begin()
如果错误!=零{
返回错误。换行(错误,“开始事务”)
}
txOK:=假
延迟函数(){
如果!txOK{
tx.回滚()
}
}()
//末尾的ON COMMIT DROP子句确保表
//在事务结束时进行清理。
//而负责{..}状态机的goroutine延迟了
//保存可确保此功能在任何给定时间都不会运行两次。
_,err=tx.Exec(sqlfdatamaketentable)
//创建临时表fstore\u data\u load
//(映射文本不为NULL、键文本不为NULL、数据json)
//提交丢弃时
如果错误!=零{
返回errors.Wrap(err,“创建临时表”)
}
stmt,err:=tx.Prepare(pq.CopyIn(_sqlfdatatestablename,“map”,“key”,“data”))
对于键,val:=范围项{
_,err=stmt.Exec(string(key.Map)、string(key.key)、string(val))
如果错误!=零{
返回错误。换行(错误,“加载副本数据”)
}
}
_,err=stmt.Exec()
如果错误!=零{
返回错误。换行(错误,“刷新复制数据”)
}
err=stmt.Close()
如果错误!=零{
返回错误。换行(错误,“关闭复制stmt”)
}
_,err=tx.Exec(sqlFDataSetFromTemp)
//插入fstore_数据(地图、键、数据)
//从fstore\u data\u load中选择映射、键、数据
//发生冲突时,是否更新集数据=排除。数据
如果错误!=零{
返回errors.Wrap(err,“从临时表移动到实际表”)
}
err=tx.Commit()
如果错误!=零{
返回错误。Wrap(err,“提交事务”)
}
txOK=true
归零
}
适应PostgreSQL,它不支持
占位符,以下工作:

func BulkInsert(unsavedRows []*ExampleRowStruct) error {
    valueStrings := make([]string, 0, len(unsavedRows))
    valueArgs := make([]interface{}, 0, len(unsavedRows) * 3)
    i := 0
    for _, post := range unsavedRows {
        valueStrings = append(valueStrings, fmt.Sprintf("($%d, $%d, $%d)", i*3+1, i*3+2, i*3+3))
        valueArgs = append(valueArgs, post.Column1)
        valueArgs = append(valueArgs, post.Column2)
        valueArgs = append(valueArgs, post.Column3)
        i++
    }
    stmt := fmt.Sprintf("INSERT INTO my_sample_table (column1, column2, column3) VALUES %s", strings.Join(valueStrings, ","))
    _, err := db.Exec(stmt, valueArgs...)
    return err
}

以Andrew C为例,使用sql标量变量对其进行调整,以满足我工作中的需要。它非常适合我工作中的特定需求。也许它对某些人有用,因为它可以在golang中模拟sql的批处理事务。这就是想法

func BulkInsert(unsavedRows []*ExampleRowStruct) error {
    valueStrings := make([]string, 0, len(unsavedRows))
    valueArgs := make([]interface{}, 0, len(unsavedRows) * 3)
    i := 0
    for _, post := range unsavedRows {
        valueStrings = append(valueStrings, fmt.Sprintf("(@p%d, @p%d, @p%d)", i*3+1, i*3+2, i*3+3))
        valueArgs = append(valueArgs, post.Column1)
        valueArgs = append(valueArgs, post.Column2)
        valueArgs = append(valueArgs, post.Column3)
        i++
    }
    sqlQuery := fmt.Sprintf("INSERT INTO my_sample_table (column1, column2, column3) VALUES %s", strings.Join(valueStrings, ","))

    var params []interface{}

    for i := 0; i < len(valueArgs); i++ {
        var param sql.NamedArg
        param.Name = fmt.Sprintf("p%v", i+1)
        param.Value = valueArgs[i]
        params = append(params, param)
    }

    _, err := db.Exec(sqlQuery, params...)
    return err
}
func BulkInsert(unsavedRows[]*ExampleRowStruct)错误{
valueStrings:=make([]字符串,0,len(未保存行))
valueArgs:=make([]接口{},0,len(未保存行)*3)
i:=0
对于u,post:=范围未保存行{
valueStrings=append(valueStrings,fmt.Sprintf(@p%d,@p%d,@p%d)”,i*3+1,i*3+2,i*3+3))
valueArgs=append(valueArgs,post.Column1)
valueArgs=append(valueArgs,post.Column2)
valueArgs=append(valueArgs,post.Column3)
我++
}
sqlQuery:=fmt.Sprintf(“插入到我的样本表格(第1列、第2列、第3列)中的值%s”,strings.Join(valueStrings,”,“”)
var params[]接口{}
对于i:=0;i
对于Postgres lib pq,支持批量插入:

但通过下面的代码也可以实现这一点,但真正有用的是当您尝试执行批量条件更新(相应地更改查询)时

要对Postgres执行类似的批量插入,可以使用以下函数

// ReplaceSQL replaces the instance occurrence of any string pattern with an increasing $n based sequence
func ReplaceSQL(old, searchPattern string) string {
   tmpCount := strings.Count(old, searchPattern)
   for m := 1; m <= tmpCount; m++ {
      old = strings.Replace(old, searchPattern, "$"+strconv.Itoa(m), 1)
   }
   return old
}
如果你使用Postgres,这里有一个解决方案

功能示例:

另一个例子:

func-ReplaceSQL

func ReplaceSQL(stmt, pattern string, len int) string {
    pattern += ","
    stmt = fmt.Sprintf(stmt, strings.Repeat(pattern, len))
    n := 0
    for strings.IndexByte(stmt, '?') != -1 {
        n++
        param := "$" + strconv.Itoa(n)
        stmt = strings.Replace(stmt, "?", param, 1)
    }
    return strings.TrimSuffix(stmt, ",")
}

链语法还有一个很好的库,可以查看
go-pg

使用单个查询插入多个图书:

err := db.Model(book1, book2).Insert()

下面是一个更通用的版本,用于根据@andrew-c和@mastercarl的答案生成查询和值参数:

//bulk/insert.go

import (
    "strconv"
    "strings"
)

type ValueExtractor = func(int) []interface{}

func Generate(tableName string, columns []string, numRows int, postgres bool, valueExtractor ValueExtractor) (string, []interface{}) {
    numCols := len(columns)
    var queryBuilder strings.Builder
    queryBuilder.WriteString("INSERT INTO ")
    queryBuilder.WriteString(tableName)
    queryBuilder.WriteString("(")
    for i, column := range columns {
        queryBuilder.WriteString("\"")
        queryBuilder.WriteString(column)
        queryBuilder.WriteString("\"")
        if i < numCols-1 {
            queryBuilder.WriteString(",")
        }
    }
    queryBuilder.WriteString(") VALUES ")
    var valueArgs []interface{}
    valueArgs = make([]interface{}, 0, numRows*numCols)
    for rowIndex := 0; rowIndex < numRows; rowIndex++ {
        queryBuilder.WriteString("(")
        for colIndex := 0; colIndex < numCols; colIndex++ {
            if postgres {
                queryBuilder.WriteString("$")
                queryBuilder.WriteString(strconv.Itoa(rowIndex*numCols + colIndex + 1))
            } else {
                queryBuilder.WriteString("?")
            }
            if colIndex < numCols-1 {
                queryBuilder.WriteString(",")
            }
        }
        queryBuilder.WriteString(")")
        if rowIndex < numRows-1 {
            queryBuilder.WriteString(",")
        }
        valueArgs = append(valueArgs, valueExtractor(rowIndex)...)
    }
    return queryBuilder.String(), valueArgs
}

我得到了pq.CopyIn,它实际上比字符串值/args方法快2.4倍(顺便说一句,这是一个非常有用的优雅解决方案,谢谢!)

err := db.Model(book1, book2).Insert()
import (
    "strconv"
    "strings"
)

type ValueExtractor = func(int) []interface{}

func Generate(tableName string, columns []string, numRows int, postgres bool, valueExtractor ValueExtractor) (string, []interface{}) {
    numCols := len(columns)
    var queryBuilder strings.Builder
    queryBuilder.WriteString("INSERT INTO ")
    queryBuilder.WriteString(tableName)
    queryBuilder.WriteString("(")
    for i, column := range columns {
        queryBuilder.WriteString("\"")
        queryBuilder.WriteString(column)
        queryBuilder.WriteString("\"")
        if i < numCols-1 {
            queryBuilder.WriteString(",")
        }
    }
    queryBuilder.WriteString(") VALUES ")
    var valueArgs []interface{}
    valueArgs = make([]interface{}, 0, numRows*numCols)
    for rowIndex := 0; rowIndex < numRows; rowIndex++ {
        queryBuilder.WriteString("(")
        for colIndex := 0; colIndex < numCols; colIndex++ {
            if postgres {
                queryBuilder.WriteString("$")
                queryBuilder.WriteString(strconv.Itoa(rowIndex*numCols + colIndex + 1))
            } else {
                queryBuilder.WriteString("?")
            }
            if colIndex < numCols-1 {
                queryBuilder.WriteString(",")
            }
        }
        queryBuilder.WriteString(")")
        if rowIndex < numRows-1 {
            queryBuilder.WriteString(",")
        }
        valueArgs = append(valueArgs, valueExtractor(rowIndex)...)
    }
    return queryBuilder.String(), valueArgs
}
import (
    "fmt"
    "strconv"
)

func valueExtractor(index int) []interface{} {
    return []interface{}{
        "trx-" + strconv.Itoa(index),
        "name-" + strconv.Itoa(index),
        index,
    }
}

func ExampleGeneratePostgres() {
    query, valueArgs := Generate("tbl_persons", []string{"transaction_id", "name", "age"}, 3, true, valueExtractor)
    fmt.Println(query)
    fmt.Println(valueArgs)
    // Output:
    // INSERT INTO tbl_persons("transaction_id","name","age") VALUES ($1,$2,$3),($4,$5,$6),($7,$8,$9)
    // [[trx-0 name-0 0] [trx-1 name-1 1] [trx-2 name-2 2]]
}

func ExampleGenerateOthers() {
    query, valueArgs := Generate("tbl_persons", []string{"transaction_id", "name", "age"}, 3, false, valueExtractor)
    fmt.Println(query)
    fmt.Println(valueArgs)
    // Output:
    // INSERT INTO tbl_persons("transaction_id","name","age") VALUES (?,?,?),(?,?,?),(?,?,?)
    // [[trx-0 name-0 0] [trx-1 name-1 1] [trx-2 name-2 2]]
}
func copyData(client *client.DbClient, dataModels []*dataModel) error{
    db := *client.DB
    txn, err := db.Begin()
    if err != nil {
        return err
    }
    defer txn.Commit()

    stmt, err := txn.Prepare(pq.CopyIn("_temp", "a", "b"))
    if err != nil {
        return(err)
    }

    for _, model := range dataModels{
        _, err := stmt.Exec(model.a, model.b)
        if err != nil {
            txn.Rollback()
            return err
        }
    }

    _, err = stmt.Exec()
    if err != nil {
        return err
    }

    err = stmt.Close()
    if err != nil {
        return err
    }

    return nil
    }