Operações Avançadas com sqlx.In em Go

Introdução ao sqlx.In

O sqlx é um pacote para Go que fornece uma série de extensões sobree o exceelnte pacote database/sql embutido. O sqlx.In é particularmente útil para lidar com consultas SQL que contêm cláusulas IN.

Consultas "IN"

O pacote database/sql não insepciona a consulta e passa os argumentos diretamente para o driver, tornando difícil lidar com consultas que contêm cláusulas IN:

SELECT * FROM users WHERE level IN (?);

Quando essa consulta é preparada, o marcador de posição ? corresponde a um único argumento, mas muitas vezes desejamos que ele corresponda a um número variável de argumentos, dependendo do comprimento de uma fatia, por exemplo:

var niveis = []int{4, 6, 7}
query, args, err := sqlx.In("SELECT * FROM users WHERE level IN (?);", niveis)
if err != nil {
    // Tratar erro
}
query = db.Rebind(query)
rows, err := db.Query(query, args...)
if err != nil {
    // Tratar erro
}

O sqlx.In expande os marcadores de posição na consulta que correspondem a uma fatia nos argumentos, substituindo-os pelos elementos da fatia. Ele faz isso apenas com o marcador de posição ?, e você pode usar db.Rebind para obter uma consulta adequada para o seu backend.

Inserção em Lote sem sqlx.In

Aqui está um exemplo de inserção em lote sem o uso de sqlx.In:

package main

import (
    "database/sql"
    "fmt"
    "strings"
    "time"

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

var db *sql.DB

func initMySQL() (err error) {
    dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test"
    db, err = sql.Open("mysql", dsn)
    if err != nil {
        return err
    }
    err = db.Ping()
    if err != nil {
        fmt.Printf("Erro ao conectar ao banco de dados: %v\n", err)
        return err
    }
    db.SetConnMaxLifetime(time.Second * 10)
    db.SetConnMaxIdleTime(time.Second * 5)
    db.SetMaxOpenConns(200)
    db.SetMaxIdleConns(10)
    return nil
}

type Usuario struct {
    Nome string `db:"nome"`
    Idade int    `db:"idade"`
}

func InserirUsuariosEmLote(usuarios []*Usuario) error {
    valueStrings := make([]string, 0, len(usuarios))
    valueArgs := make([]interface{}, 0, len(usuarios)*2)

    for _, u := range usuarios {
        valueStrings = append(valueStrings, "(?, ?)")
        valueArgs = append(valueArgs, u.Nome, u.Idade)
    }
    stmt := fmt.Sprintf("INSERT INTO usuario (nome, idade) VALUES %s", strings.Join(valueStrings, ","))
    result, err := db.Exec(stmt, valueArgs...)
    if err != nil {
        fmt.Printf("Erro ao inserir usuários no banco de dados: %v \n", err)
        return err
    }
    rowsAffected, err := result.RowsAffected()
    if err != nil {
        fmt.Printf("Erro ao obter linhas afetadas: %v\n", err)
        return err
    }
    fmt.Println("Linhas afetadas: ", rowsAffected)
    return nil
}

func main() {
    if err := initMySQL(); err != nil {
        fmt.Printf("Erro ao conectar ao banco de dados: %v\n", err)
    }
    defer db.Close()

    fmt.Println("Conexão com o banco de dados estabelecida")
    usuarios := []*Usuario{
        {Nome: "Liubei", Idade: 25},
        {Nome: "Guanyu", Idade: 30},
        {Nome: "Zhangfei", Idade: 28},
    }
    err := InserirUsuariosEmLote(usuarios)
    if err != nil {
        fmt.Printf("Falha ao inserir usuários em lote: %v", err)
    }
}

Inserção em Lote com sqlx.In

Aqui está um exemplo de inserção em lote usando sqlx.In:

package main

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

var db *sqlx.DB

func initDB() (err error) {
    dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
    db, err = sqlx.Connect("mysql", dsn)
    if err != nil {
        fmt.Printf("Erro ao conectar ao banco de dados: %v\n", err)
        return
    }
    db.SetMaxOpenConns(20)
    db.SetMaxIdleConns(10)
    return
}

type Usuario struct {
    ID   int    `db:"id"`
    Idade int    `db:"idade"`
    Nome string `db:"nome"`
}

func (u Usuario) Value() (driver.Value, error) {
    return []interface{}{u.Nome, u.Idade}, nil
}

func InserirUsuariosEmLoteComSqlxIn(usuarios []interface{}) error {
    query, args, _ := sqlx.In(
        "INSERT INTO usuario (nome, idade) VALUES (?), (?), (?)",
        usuarios...,
    )
    fmt.Println("Consulta SQL: ", query)
    fmt.Println("Argumentos: ", args)
    result, err := db.Exec(query, args...)
    if err != nil {
        fmt.Printf("Erro ao executar a consulta: %v\n", err)
        return err
    }
    rowsAffected, err := result.RowsAffected()
    if err != nil {
        fmt.Printf("Erro ao obter linhas afetadas: %v\n", err)
        return err
    }
    fmt.Println("Linhas afetadas: ", rowsAffected)
    return nil
}

func main() {
    if err := initDB(); err != nil {
        fmt.Printf("Erro ao inicializar o banco de dados: %v\n", err)
        return
    }
    fmt.Println("Banco de dados inicializado com sucesso")

    u1 := Usuario{Nome: "Libai", Idade: 16}
    u2 := Usuario{Nome: "Dufu", Idade: 42}
    u3 := Usuario{Nome: "Wangwei", Idade: 29}
    usuarios := []interface{}{u1, u2, u3}
    _ = InserirUsuariosEmLoteComSqlxIn(usuarios)
}

Consultas "IN" com sqlx

Consultar IDs em um conjunto específico

package main

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

var db *sqlx.DB

func initDB() (err error) {
    dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
    db, err = sqlx.Connect("mysql", dsn)
    if err != nil {
        fmt.Printf("Erro ao conectar ao banco de dados: %v\n", err)
        return
    }
    db.SetMaxOpenConns(20)
    db.SetMaxIdleConns(10)
    return
}

type Usuario struct {
    ID   int    `db:"id"`
    Idade int    `db:"idade"`
    Nome string `db:"nome"`
}

func ConsultarPorIDs(ids []int) (usuarios []Usuario, err error) {
    query, args, err := sqlx.In("SELECT nome, idade FROM usuario WHERE id IN (?)", ids)
    if err != nil {
        return nil, err
    }
    query = db.Rebind(query)
    err = db.Select(&usuarios, query, args...)
    if err != nil {
        return nil, err
    }
    return usuarios, nil
}

func main() {
    if err := initDB(); err != nil {
        fmt.Printf("Erro ao inicializar o banco de dados: %v\n", err)
        return
    }
    fmt.Println("Banco de dados inicializado com sucesso")

    usuarios, err := ConsultarPorIDs([]int{1, 15, 21, 2})
    if err != nil {
        fmt.Printf("Erro na consulta: %v\n", err)
        return
    }
    fmt.Printf("Resultados da consulta: %v\n", usuarios)
    for _, usuario := range usuarios {
        fmt.Printf("Usuário: %#v\n", usuario)
    }
}

Ordenação com FIND_IN_SET

Para ordenar os resultados de uma consulta "IN" de acordo com uma ordem específica, podemos usar a função FIND_IN_SET do MySQL:

package main

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

var db *sqlx.DB

func initDB() (err error) {
    dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
    db, err = sqlx.Connect("mysql", dsn)
    if err != nil {
        fmt.Printf("Erro ao conectar ao banco de dados: %v\n", err)
        return
    }
    db.SetMaxOpenConns(20)
    db.SetMaxIdleConns(10)
    return
}

type Usuario struct {
    ID   int    `db:"id"`
    Idade int    `db:"idade"`
    Nome string `db:"nome"`
}

func ConsultarPorIDs(ids []int) (usuarios []Usuario, err error) {
    query, args, err := sqlx.In("SELECT nome, idade FROM usuario WHERE id IN (?)", ids)
    if err != nil {
        return nil, err
    }
    query = db.Rebind(query)
    err = db.Select(&usuarios, query, args...)
    if err != nil {
        return nil, err
    }
    return usuarios, nil
}

func ConsultarEOrdenarPorIDs(ids []int) (usuarios []Usuario, err error) {
    strIDs := make([]string, 0, len(ids))
    for _, id := range ids {
        strIDs = append(strIDs, fmt.Sprintf("%d", id))
    }
    query, args, err := sqlx.In("SELECT nome, idade FROM usuario WHERE id IN (?) ORDER BY FIND_IN_SET(id, ?)", ids, strings.Join(strIDs, ","))
    if err != nil {
        return
    }
    query = db.Rebind(query)
    err = db.Select(&usuarios, query, args...)
    return
}

func main() {
    if err := initDB(); err != nil {
        fmt.Printf("Erro ao inicializar o banco de dados: %v\n", err)
        return
    }
    fmt.Println("Banco de dados inicializado com sucesso")

    usuarios, err := ConsultarPorIDs([]int{1, 15, 21, 2})
    if err != nil {
        fmt.Printf("Erro na consulta: %v\n", err)
        return
    }
    fmt.Printf("Resultados da consulta: %v\n", usuarios)
    for _, usuario := range usuarios {
        fmt.Printf("Usuário: %#v\n", usuario)
    }

    fmt.Println("**************")
    usuarios, err = ConsultarEOrdenarPorIDs([]int{1, 15, 21, 2})
    if err != nil {
        fmt.Printf("Erro na consulta: %v\n", err)
        return
    }
    fmt.Printf("Resultados da consulta: %v\n", usuarios)
    for _, usuario := range usuarios {
        fmt.Printf("Usuário: %#v\n", usuario)
    }
}

Tags: go sqlx MySQL database/sql sqlx.In

Publicado em 10-3 13:06