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