Go è¯Âè¨Âä¹ SQLX é«Â级æÂÂä½ sqlx.In
sqlx.In ä»Âç»Â
sqlx
is a package for Go which provides a set of extensions on top of the excellent built-in database/sql
package.
Illustrated guide to SQLXï¼Âjmoiron.github.io/sqlx/
sqlxï¼Âgithub.com/jmoiron/sqlâ¦
“In” Queries
Because database/sql
does not inspect your query and it passes your arguments directly to the driver, it makes dealing with queries with IN clauses difficult:
SELECT * FROM users WHERE level IN (?);
When this gets prepared as a statement on the backend, the bindvar ?
will only correspond to a single argument, but what is often desired is for that to be a variable number of arguments depending on the length of some slice, eg:
var levels = []int{4, 6, 7}rows, err := db.Query("SELECT * FROM users WHERE level IN (?);", levels)
This pattern is possible by first processing the query with sqlx.In
:
var levels = []int{4, 6, 7}query, args, err := sqlx.In("SELECT * FROM users WHERE level IN (?);", levels) // sqlx.In returns queries with the `?` bindvar, we can rebind it for our backendquery = db.Rebind(query)rows, err := db.Query(query, args...)
What sqlx.In
does is expand any bindvars in the query passed to it that correspond to a slice in the arguments to the length of that slice, and then append those slice elements to a new arglist. It does this with the ?
bindvar only; you can use db.Rebind
to get a query suitable for your backend.
æ®éÂÂæ¹éÂÂæÂÂå ¥æ°æ®ï¼Âä¸Â使ç¨ sqlx.In
package main
âÂÂ
import (
"database/sql"
"fmt"
"strings"
"time"
âÂÂ
_ "github.com/go-sql-driver/mysql" // å¿åÂÂ导åÂ
Â¥ èªå¨æ§衠init()
)
âÂÂ
var db *sql.DB
âÂÂ
func initMySQL() (err error) {
//DSN (Data Source Name)
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test"
// 注æÂÂï¼Âè¦ÂÃ¥ÂÂå§ÂÃ¥ÂÂÃ¥Â
¨å±Âç db 对象ï¼Âä¸Âè¦Âæ°声æÂÂä¸Â个 db Ã¥ÂÂéÂÂ
db, err = sql.Open("mysql", dsn) // åª对格å¼Âè¿Âè¡Âæ ¡éªÂï¼Â并ä¸Âä¼ÂçÂÂæ£è¿ÂæÂ¥æ°æ®åºÂ
if err != nil {
return err
}
âÂÂ
// Ping éªÂè¯Âä¸Âæ°æ®åºÂçÂÂè¿ÂæÂ¥æ¯å¦ä»Âå¤ÂäºÂæ´»å¨ç¶æÂÂï¼Â并å¨å¿Â
è¦Âæ¶建ç«Âè¿ÂæÂ¥ãÂÂ
err = db.Ping()
if err != nil {
fmt.Printf("connect to db failed, err: %v\n", err)
return err
}
// æ°å¼éÂÂè¦Âæ ¹æ®ä¸Âå¡åÂ
·ä½ÂæÂÂ
åµæ¥确å®Â
db.SetConnMaxLifetime(time.Second * 10) // 设置å¯以éÂÂç¨è¿ÂæÂ¥çÂÂæÂÂé¿æ¶é´
db.SetConnMaxIdleTime(time.Second * 5) à// 设置è¿ÂæÂ¥å¯è½å¤ÂäºÂ空é²ç¶æÂÂçÂÂæÂÂé¿æ¶é´
db.SetMaxOpenConns(200) àààààààà// 设置ä¸Âæ°æ®åºÂçÂÂæÂÂ大æÂÂå¼Âè¿ÂæÂ¥æ°
db.SetMaxIdleConns(10) ààààààààà// 设置空é²è¿Âæ¥池ä¸ÂçÂÂæÂÂ大è¿ÂæÂ¥æ°
return nil
}
âÂÂ
type User struct {
Name string `db:"name"`
Age ÃÂ int ÃÂ ÃÂ `db:"age"`
}
âÂÂ
// BatchInsertUsers æ¹éÂÂæÂÂÃ¥Â
¥æ°æ®
func BatchInsertUsers(users []*User) error {
valueStrings := make([]string, 0, len(users)) àà// å ä½Â符 slice
valueArgs := make([]interface{}, 0, len(users)*2) // æÂÂÃ¥Â
¥å¼ slice
âÂÂ
for _, u := range users {
valueStrings = append(valueStrings, "(?, ?)")
valueArgs = append(valueArgs, u.Name, u.Age) // å ä½Â符ä¸ÂæÂÂÃ¥Â
¥å¼ ä¸Âä¸Â对åºÂ
}
// æ¼æÂ¥å®Âæ´çÂÂSQLè¯ÂÃ¥ÂÂ¥
// Sprintfæ ¹æ®格å¼Â说æÂÂ符è¿Âè¡Âæ ¼å¼ÂÃ¥ÂÂï¼Â并è¿ÂÃ¥ÂÂç»ÂæÂÂÃ¥ÂÂ符串ãÂÂ
// Joinå°ÂÃ¥Â
¶ç¬¬ä¸Â个åÂÂæ°çÂÂÃ¥Â
Âç´ è¿Âæ¥起æ¥以åÂÂ建åÂÂ个åÂÂ符串ãÂÂÃ¥ÂÂéÂÂÃ¥ÂÂ符串sepæ¾置å¨ç»ÂæÂÂÃ¥ÂÂ符串çÂÂÃ¥Â
Âç´ ä¹Âé´ãÂÂ
stmt := fmt.Sprintf("INSERT INTO user (name, age) VALUES %s", strings.Join(valueStrings, ","))
// Execæ§è¡Âæ¥询èÂÂä¸Âè¿ÂÃ¥ÂÂä»»ä½Âè¡ÂãÂÂÃ¥ÂÂæ°ç¨äºÂæ¥询ä¸ÂçÂÂä»»ä½Âå ä½Â符åÂÂæ°ãÂÂ
result, err := db.Exec(stmt, valueArgs...)
if err != nil {
fmt.Printf("Error inserting user into database: %v \n", err)
return err
}
var rows_affected int64
rows_affected, err = result.RowsAffected() // è¿ÂÃ¥ÂÂÃ¥ÂÂæ´æ°ãÂÂæÂÂÃ¥Â
¥æÂÂå é¤影åÂÂçÂÂè¡Âæ°ãÂÂ并éÂÂæ¯Â个æ°æ®åºÂæÂÂæ°æ®åºÂ驱å¨ç¨ÂåºÂé½æ¯æÂÂæ¤åÂÂè½ãÂÂ
if err != nil {
fmt.Printf("è¿ÂÃ¥ÂÂÃ¥ÂÂæ´æ°ãÂÂæÂÂÃ¥Â
¥æÂÂå é¤影åÂÂçÂÂè¡Âæ° failed, err: %v\n", err)
return err
}
fmt.Println("Ã¥ÂÂæ´æ°ãÂÂæÂÂÃ¥Â
¥æÂÂå é¤影åÂÂçÂÂè¡Âæ°: ", rows_affected)
return nil
}
âÂÂ
func main() {
if err := initMySQL(); err != nil {
fmt.Printf("connect to db failed, err: %v\n", err)
}
// æ£ÂæÂ¥å®ÂéÂÂ误ä¹ÂÃ¥ÂÂæ§è¡Âï¼Â确俠db ä¸Â为 nil
// Close() ç¨æÂ¥éÂÂæ¾æ°æ®åºÂè¿ÂæÂ¥ç¸åÂ
³çÂÂèµÂæºÂ
// Close å°ÂÃ¥Â
³éÂÂæ°æ®åºÂ并éÂȾ¢å¯å¨æ°æ¥询ãÂÂÃ¥Â
³éÂÂï¼Âç¶åÂÂçÂÂå¾Â
æÂÂå¡å¨ä¸Âå·²å¼Âå§Âå¤ÂçÂÂçÂÂæÂÂæÂÂæ¥询å®ÂæÂÂãÂÂ
defer db.Close()
âÂÂ
fmt.Println("connect to database success")
// db.xx() å»使ç¨æ°æ®åºÂæÂÂä½Â...
âÂÂ
// æ¹éÂÂæÂÂÃ¥Â
¥æ°æ®
users := []*User{
{Name: "Ã¥ÂÂå¤Â", Age: 25},
{Name: "Ã¥Â
³ç¾½", Age: 30},
{Name: "å¼ é£Â", Age: 28},
}
err := BatchInsertUsers(users)
if err != nil {
fmt.Printf("Failed to batch insert users: %v", err)
}
}
âÂÂ
è¿Âè¡Â
Code/go/mysql_demo via ð¹ v1.20.3 via ðÂÂ
 base
â go run main.go
connect to database success
Ã¥ÂÂæ´æ°ãÂÂæÂÂÃ¥Â
¥æÂÂå é¤影åÂÂçÂÂè¡Âæ°: à3
âÂÂ
Code/go/mysql_demo via ð¹ v1.20.3 via ðÂÂ
 base
âÂÂ
SQL æ¥询ç»ÂæÂÂ
mysql> select * from user; à# æÂÂÃ¥Â
¥ä¹ÂÃ¥ÂÂ
+----+--------+------+
| id | name ÃÂ | age ÃÂ |
+----+--------+------+
| à1 | å°Â习à| à12 |
| à2 | å°Â习à| à22 |
| à5 | æÂÂå à| à18 |
| à6 | é»Âç à| à16 |
| à8 | æÂÂçÂ
 Ã | à26 |
| ÃÂ 9 | Alice ÃÂ | ÃÂ 25 |
| 10 | Bob ÃÂ ÃÂ | ÃÂ 30 |
| 11 | Carol ÃÂ | ÃÂ 28 |
| 12 | Alice1 | ÃÂ 25 |
| 13 | Bob1 ÃÂ | ÃÂ 30 |
| 14 | Carol1 | ÃÂ 28 |
+----+--------+------+
11 rows in set (0.00 sec)
âÂÂ
mysql> select * from user; à# æÂÂÃ¥Â
¥ä¹ÂÃ¥ÂÂ
+----+--------+------+
| id | name ÃÂ | age ÃÂ |
+----+--------+------+
| à1 | å°Â习à| à12 |
| à2 | å°Â习à| à22 |
| à5 | æÂÂå à| à18 |
| à6 | é»Âç à| à16 |
| à8 | æÂÂçÂ
 Ã | à26 |
| ÃÂ 9 | Alice ÃÂ | ÃÂ 25 |
| 10 | Bob ÃÂ ÃÂ | ÃÂ 30 |
| 11 | Carol ÃÂ | ÃÂ 28 |
| 12 | Alice1 | ÃÂ 25 |
| 13 | Bob1 ÃÂ | ÃÂ 30 |
| 14 | Carol1 | ÃÂ 28 |
| 15 | Ã¥ÂÂ夠à| à25 |
| 16 | Ã¥Â
³ç¾½ à| à30 |
| 17 | 张飠à| à28 |
+----+--------+------+
14 rows in set (0.01 sec)
âÂÂ
使ç¨ 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"
// è¿ÂæÂ¥å°æ°æ®åºÂ并使ç¨pingè¿Âè¡ÂéªÂè¯ÂãÂÂ
// ä¹Âå¯以使ç¨ MustConnect MustConnectè¿ÂæÂ¥å°æ°æ®åºÂï¼Â并å¨åºç°éÂÂ误æ¶æÂÂæÂ
 panicãÂÂ
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 设置æ°æ®åºÂçÂÂæÂÂ大æÂÂå¼Âè¿ÂæÂ¥æ°ãÂÂ
db.SetMaxIdleConns(10) // 设置空é²è¿Âæ¥池ä¸ÂçÂÂæÂÂ大è¿ÂæÂ¥æ°ãÂÂ
return
}
âÂÂ
type user struct {
ID ÃÂ int ÃÂ ÃÂ `db:"id"`
Age ÃÂ int ÃÂ ÃÂ `db:"age"`
Name string `db:"name"`
}
âÂÂ
func (u user) Value() (driver.Value, error) {
return []interface{}{u.Name, u.Age}, nil
}
âÂÂ
// BatchInsertUsersSqlxIn 使ç¨sqlx.In帮æÂÂ们æ¼æÂ¥è¯Âå¥åÂÂÃ¥ÂÂæ°, 注æÂÂä¼ åÂ
¥çÂÂÃ¥ÂÂæ°æ¯[]interface{}
func BatchInsertUsersSqlxIn(users []interface{}) error {
// Inå±Âå¼Âargsä¸ÂçÂÂÃ¥ÂÂçÂÂå¼ï¼Âè¿ÂÃ¥ÂÂä¿®æ¹åÂÂçÂÂæ¥询åÂÂ符串åÂÂä¸Â个å¯以ç±æ°æ®åºÂæ§è¡ÂçÂÂæ°çÂÂargÃ¥ÂÂ表ãÂÂ
// âÂÂæ¥询âÂÂåºÂ该使ç¨âÂÂ?âÂÂâÂÂbindVarãÂÂè¿ÂÃ¥ÂÂå¼使ç¨' ?âÂÂbindVarãÂÂ
query, args, _ := sqlx.In(
"INSERT INTO user (name, age) VALUES (?), (?), (?)",
users..., // å¦ÂæÂÂargå®Âç°亠driver.Valuer, sqlx.In ä¼ÂéÂÂè¿Âè°Âç¨ Value()æÂ¥å±Âå¼Âå®Â
)
fmt.Println("query sql string: ", query) // æÂ¥çÂÂçÂÂæÂÂçÂÂquerystring
fmt.Println("args: ", args) ààààààà// æÂ¥çÂÂçÂÂæÂÂçÂÂargs
// Execæ§è¡Âæ¥询èÂÂä¸Âè¿ÂÃ¥ÂÂä»»ä½Âè¡ÂãÂÂÃ¥ÂÂæ°ç¨äºÂæ¥询ä¸ÂçÂÂä»»ä½Âå ä½Â符åÂÂæ°ãÂÂ
result, err := db.Exec(query, args...)
var rows_affected int64
rows_affected, err = result.RowsAffected() // è¿ÂÃ¥ÂÂÃ¥ÂÂæ´æ°ãÂÂæÂÂÃ¥Â
¥æÂÂå é¤影åÂÂçÂÂè¡Âæ°ãÂÂ并éÂÂæ¯Â个æ°æ®åºÂæÂÂæ°æ®åºÂ驱å¨ç¨ÂåºÂé½æ¯æÂÂæ¤åÂÂè½ãÂÂ
if err != nil {
fmt.Printf("è¿ÂÃ¥ÂÂÃ¥ÂÂæ´æ°ãÂÂæÂÂÃ¥Â
¥æÂÂå é¤影åÂÂçÂÂè¡Âæ° failed, err: %v\n", err)
return err
}
fmt.Println("Ã¥ÂÂæ´æ°ãÂÂæÂÂÃ¥Â
¥æÂÂå é¤影åÂÂçÂÂè¡Âæ°: ", rows_affected)
return nil
}
âÂÂ
âÂÂ
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// æ¹éÂÂæÂÂÃ¥Â
Â¥
u1 := user{Name: "æÂÂç½", Age: 16}
u2 := user{Name: "æÂÂç«", Age: 42}
u3 := user{Name: "çÂÂç»´", Age: 29}
users := []interface{}{u1, u2, u3}
_ = BatchInsertUsersSqlxIn(users)
}
âÂÂ
è¿Âè¡Â
Code/go/sqlx_demo via ð¹ v1.20.3 via ðÂÂ
 base
â go run main.go
init DB succeeded
query sql string: INSERT INTO user (name, age) VALUES (?, ?), (?, ?), (?, ?)
args: [æÂÂç½ 16 æÂÂç« 42 çÂÂç»´ 29]
Ã¥ÂÂæ´æ°ãÂÂæÂÂÃ¥Â
¥æÂÂå é¤影åÂÂçÂÂè¡Âæ°: à3
âÂÂ
Code/go/sqlx_demo via ð¹ v1.20.3 via ðÂÂ
 base
SQL æ¥询ç»ÂæÂÂ
mysql> select * from user;
+----+--------+------+
| id | name ÃÂ | age ÃÂ |
+----+--------+------+
| à1 | å°Â习à| à12 |
| à2 | å°Â习à| à22 |
| à5 | æÂÂå à| à18 |
| à6 | é»Âç à| à16 |
| à8 | æÂÂçÂ
 Ã | à26 |
| ÃÂ 9 | Alice ÃÂ | ÃÂ 25 |
| 10 | Bob ÃÂ ÃÂ | ÃÂ 30 |
| 11 | Carol ÃÂ | ÃÂ 28 |
| 12 | Alice1 | ÃÂ 25 |
| 13 | Bob1 ÃÂ | ÃÂ 30 |
| 14 | Carol1 | ÃÂ 28 |
| 15 | Ã¥ÂÂ夠à| à25 |
| 16 | Ã¥Â
³ç¾½ à| à30 |
| 17 | 张飠à| à28 |
+----+--------+------+
14 rows in set (0.01 sec)
âÂÂ
mysql> select * from user; à# æÂÂÃ¥Â
¥ä¹ÂÃ¥ÂÂ
+----+--------+------+
| id | name ÃÂ | age ÃÂ |
+----+--------+------+
| à1 | å°Â习à| à12 |
| à2 | å°Â习à| à22 |
| à5 | æÂÂå à| à18 |
| à6 | é»Âç à| à16 |
| à8 | æÂÂçÂ
 Ã | à26 |
| ÃÂ 9 | Alice ÃÂ | ÃÂ 25 |
| 10 | Bob ÃÂ ÃÂ | ÃÂ 30 |
| 11 | Carol ÃÂ | ÃÂ 28 |
| 12 | Alice1 | ÃÂ 25 |
| 13 | Bob1 ÃÂ | ÃÂ 30 |
| 14 | Carol1 | ÃÂ 28 |
| 15 | Ã¥ÂÂ夠à| à25 |
| 16 | Ã¥Â
³ç¾½ à| à30 |
| 17 | 张飠à| à28 |
| 18 | æÂÂç½ à| à16 |
| 19 | æÂÂç« à| à42 |
| 20 | çÂÂç»´ à| à29 |
+----+--------+------+
17 rows in set (0.00 sec)
âÂÂ
mysql>
使ç¨ NamedExec æ¹éÂÂæÂÂå ¥
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"
// è¿ÂæÂ¥å°æ°æ®åºÂ并使ç¨pingè¿Âè¡ÂéªÂè¯ÂãÂÂ
// ä¹Âå¯以使ç¨ MustConnect MustConnectè¿ÂæÂ¥å°æ°æ®åºÂï¼Â并å¨åºç°éÂÂ误æ¶æÂÂæÂ
 panicãÂÂ
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 设置æ°æ®åºÂçÂÂæÂÂ大æÂÂå¼Âè¿ÂæÂ¥æ°ãÂÂ
db.SetMaxIdleConns(10) // 设置空é²è¿Âæ¥池ä¸ÂçÂÂæÂÂ大è¿ÂæÂ¥æ°ãÂÂ
return
}
âÂÂ
type user struct {
ID ÃÂ int ÃÂ ÃÂ `db:"id"`
Age ÃÂ int ÃÂ ÃÂ `db:"age"`
Name string `db:"name"`
}
âÂÂ
func (u user) Value() (driver.Value, error) {
return []interface{}{u.Name, u.Age}, nil
}
âÂÂ
âÂÂ
// BatchInsertUsersNamedExec NamedExec æ¹éÂÂæÂÂÃ¥Â
Â¥
func BatchInsertUsersNamedExec(users []*user) error {
// ä»»ä½Âå½åÂÂçÂÂå ä½Â符åÂÂæ°é½å°Â被argä¸ÂçÂÂÃ¥ÂÂ段æ¿æ¢ãÂÂ
result, err := db.NamedExec("INSERT INTO user (name, age) VALUES (:name, :age)", users)
var rows_affected int64
rows_affected, err = result.RowsAffected() // è¿ÂÃ¥ÂÂÃ¥ÂÂæ´æ°ãÂÂæÂÂÃ¥Â
¥æÂÂå é¤影åÂÂçÂÂè¡Âæ°ãÂÂ并éÂÂæ¯Â个æ°æ®åºÂæÂÂæ°æ®åºÂ驱å¨ç¨ÂåºÂé½æ¯æÂÂæ¤åÂÂè½ãÂÂ
if err != nil {
fmt.Printf("è¿ÂÃ¥ÂÂÃ¥ÂÂæ´æ°ãÂÂæÂÂÃ¥Â
¥æÂÂå é¤影åÂÂçÂÂè¡Âæ° failed, err: %v\n", err)
return err
}
fmt.Println("BatchInsertUsersNamedExec Ã¥ÂÂæÂÂÃ¥Â
¥å½±åÂÂçÂÂè¡Âæ°: ", rows_affected)
return nil
}
âÂÂ
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// æ¹éÂÂæÂÂÃ¥Â
Â¥
u1 := user{Name: "è¤Âå§Â", Age: 16}
u2 := user{Name: "è²ÂèÂÂ", Age: 42}
u3 := user{Name: "é£ÂçÂÂ", Age: 29}
// NamedExec
users := []*user{&u1, &u2, &u3}
_ = BatchInsertUsersNamedExec(users)
}
âÂÂ
è¿Âè¡Â
Code/go/sqlx_demo via ð¹ v1.20.3 via ðÂÂ
 base
â go run main.go
init DB succeeded
BatchInsertUsersNamedExec Ã¥ÂÂæÂÂÃ¥Â
¥å½±åÂÂçÂÂè¡Âæ°: à3
âÂÂ
Code/go/sqlx_demo via ð¹ v1.20.3 via ðÂÂ
 base
âÂÂ
âÂÂ
SQL æ¥询ç»ÂæÂÂ
mysql> select * from user;
+----+--------+------+
| id | name ÃÂ | age ÃÂ |
+----+--------+------+
| à1 | å°Â习à| à12 |
| à2 | å°Â习à| à22 |
| à5 | æÂÂå à| à18 |
| à6 | é»Âç à| à16 |
| à8 | æÂÂçÂ
 Ã | à26 |
| ÃÂ 9 | Alice ÃÂ | ÃÂ 25 |
| 10 | Bob ÃÂ ÃÂ | ÃÂ 30 |
| 11 | Carol ÃÂ | ÃÂ 28 |
| 12 | Alice1 | ÃÂ 25 |
| 13 | Bob1 ÃÂ | ÃÂ 30 |
| 14 | Carol1 | ÃÂ 28 |
| 15 | Ã¥ÂÂ夠à| à25 |
| 16 | Ã¥Â
³ç¾½ à| à30 |
| 17 | 张飠à| à28 |
| 18 | æÂÂç½ à| à16 |
| 19 | æÂÂç« à| à42 |
| 20 | çÂÂç»´ à| à29 |
+----+--------+------+
17 rows in set (0.00 sec)
âÂÂ
mysql> select * from user; à# æÂÂÃ¥Â
¥ä¹ÂÃ¥ÂÂ
+----+--------+------+
| id | name ÃÂ | age ÃÂ |
+----+--------+------+
| à1 | å°Â习à| à12 |
| à2 | å°Â习à| à22 |
| à5 | æÂÂå à| à18 |
| à6 | é»Âç à| à16 |
| à8 | æÂÂçÂ
 Ã | à26 |
| ÃÂ 9 | Alice ÃÂ | ÃÂ 25 |
| 10 | Bob ÃÂ ÃÂ | ÃÂ 30 |
| 11 | Carol ÃÂ | ÃÂ 28 |
| 12 | Alice1 | ÃÂ 25 |
| 13 | Bob1 ÃÂ | ÃÂ 30 |
| 14 | Carol1 | ÃÂ 28 |
| 15 | Ã¥ÂÂ夠à| à25 |
| 16 | Ã¥Â
³ç¾½ à| à30 |
| 17 | 张飠à| à28 |
| 18 | æÂÂç½ à| à16 |
| 19 | æÂÂç« à| à42 |
| 20 | çÂÂç»´ à| à29 |
| 21 | è¤Â姠à| à16 |
| 22 | è²Âè à| à42 |
| 23 | é£Âç à| à29 |
+----+--------+------+
20 rows in set (0.00 sec)
âÂÂ
mysql>
sqlx é«Â级æÂÂä½Âä¹ IN æ¥询
æ¥询 ID å¨æÂÂå®ÂéÂÂÃ¥ÂÂä¸ÂçÂÂæ°æ®
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"
// è¿ÂæÂ¥å°æ°æ®åºÂ并使ç¨pingè¿Âè¡ÂéªÂè¯ÂãÂÂ
// ä¹Âå¯以使ç¨ MustConnect MustConnectè¿ÂæÂ¥å°æ°æ®åºÂï¼Â并å¨åºç°éÂÂ误æ¶æÂÂæÂ
 panicãÂÂ
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 设置æ°æ®åºÂçÂÂæÂÂ大æÂÂå¼Âè¿ÂæÂ¥æ°ãÂÂ
db.SetMaxIdleConns(10) // 设置空é²è¿Âæ¥池ä¸ÂçÂÂæÂÂ大è¿ÂæÂ¥æ°ãÂÂ
return
}
âÂÂ
type user struct {
ID ÃÂ int ÃÂ ÃÂ `db:"id"`
Age ÃÂ int ÃÂ ÃÂ `db:"age"`
Name string `db:"name"`
}
âÂÂ
// QueryByIDs æ¥询 ID å¨æÂÂå®ÂéÂÂÃ¥ÂÂä¸ÂçÂÂæ°æ®
func QueryByIDs(ids []int) (users []user, err error) {
// In å±Âå¼Âargsä¸ÂçÂÂÃ¥ÂÂçÂÂå¼ï¼Âè¿ÂÃ¥ÂÂä¿®æ¹åÂÂçÂÂæ¥询åÂÂ符串åÂÂä¸Â个å¯以ç±æ°æ®åºÂæ§è¡ÂçÂÂæ°çÂÂargÃ¥ÂÂ表ãÂÂ
// âÂÂæ¥询âÂÂåºÂ该使ç¨âÂÂ?âÂÂâÂÂbindVarãÂÂè¿ÂÃ¥ÂÂå¼使ç¨' ?âÂÂbindVar
query, args, err := sqlx.In("SELECT name, age FROM user WHERE id IN (?)", ids)
if err != nil {
return nil, err
}
// Rebind å°Âæ¥询仠QUESTION 转æ¢为DB驱å¨ç¨ÂåºÂç bindvar ç±»åÂÂãÂÂ
query = db.Rebind(query)
// Select 使ç¨æ¤æ°æ®åºÂãÂÂä»»ä½Âå ä½Â符åÂÂæ°é½å°Â被æÂÂä¾ÂçÂÂÃ¥ÂÂæ°æ¿æ¢ãÂÂ
err = db.Select(&users, query, args...)
if err != nil {
return nil, err
}
return users, nil
}
âÂÂ
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// IN æ¥询
users, err := QueryByIDs([]int{1, 15, 21, 2}) à// é»Â认æÂÂçÂ
§ä¸»é®顺åºÂæÂÂÃ¥ÂÂ
if err != nil {
fmt.Printf("query error: %v\n", err)
return
}
fmt.Printf("query successful result users %v\n", users)
for _, user := range users {
fmt.Printf("user: %#v\n", user)
}
}
âÂÂ
è¿Âè¡Â
Code/go/sqlx_demo via ð¹ v1.20.3 via ðÂÂ
 base
â go run main.go
init DB succeeded
query successful result users [{0 12 å°Âä¹Â} {0 22 å°Âä¹Â} {0 25 Ã¥ÂÂå¤Â} {0 16 è¤Âå§Â}]
user: main.user{ID:0, Age:12, Name:"å°Âä¹Â"}
user: main.user{ID:0, Age:22, Name:"å°Âä¹Â"}
user: main.user{ID:0, Age:25, Name:"Ã¥ÂÂå¤Â"}
user: main.user{ID:0, Age:16, Name:"è¤Âå§Â"}
âÂÂ
Code/go/sqlx_demo via ð¹ v1.20.3 via ðÂÂ
 base
âÂÂ
SQL æ¥询ç»ÂæÂÂ
mysql> select * from user;
+----+--------+------+
| id | name ÃÂ | age ÃÂ |
+----+--------+------+
| à1 | å°Â习à| à12 |
| à2 | å°Â习à| à22 |
| à5 | æÂÂå à| à18 |
| à6 | é»Âç à| à16 |
| à8 | æÂÂçÂ
 Ã | à26 |
| ÃÂ 9 | Alice ÃÂ | ÃÂ 25 |
| 10 | Bob ÃÂ ÃÂ | ÃÂ 30 |
| 11 | Carol ÃÂ | ÃÂ 28 |
| 12 | Alice1 | ÃÂ 25 |
| 13 | Bob1 ÃÂ | ÃÂ 30 |
| 14 | Carol1 | ÃÂ 28 |
| 15 | Ã¥ÂÂ夠à| à25 |
| 16 | Ã¥Â
³ç¾½ à| à30 |
| 17 | 张飠à| à28 |
| 18 | æÂÂç½ à| à16 |
| 19 | æÂÂç« à| à42 |
| 20 | çÂÂç»´ à| à29 |
| 21 | è¤Â姠à| à16 |
| 22 | è²Âè à| à42 |
| 23 | é£Âç à| à29 |
+----+--------+------+
20 rows in set (0.00 sec)
âÂÂ
mysql>
-
æ¥询ç»ÂæÂÂé»Â认æÂÂ砧主é®顺åºÂæÂÂÃ¥ÂÂ
-
èªå®Âä¹Âæ¥询ç»ÂæÂÂ顺åºÂ
- 使ç¨代ç ÂæÂÂåºÂ
- 使ç¨ MySQL æÂÂ庠FIND_IN_SET
sqlx é«Â级æÂÂä½Âä¹ FIND_IN_SET
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"
// è¿ÂæÂ¥å°æ°æ®åºÂ并使ç¨pingè¿Âè¡ÂéªÂè¯ÂãÂÂ
// ä¹Âå¯以使ç¨ MustConnect MustConnectè¿ÂæÂ¥å°æ°æ®åºÂï¼Â并å¨åºç°éÂÂ误æ¶æÂÂæÂ
 panicãÂÂ
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 设置æ°æ®åºÂçÂÂæÂÂ大æÂÂå¼Âè¿ÂæÂ¥æ°ãÂÂ
db.SetMaxIdleConns(10) // 设置空é²è¿Âæ¥池ä¸ÂçÂÂæÂÂ大è¿ÂæÂ¥æ°ãÂÂ
return
}
âÂÂ
type user struct {
ID ÃÂ int ÃÂ ÃÂ `db:"id"`
Age ÃÂ int ÃÂ ÃÂ `db:"age"`
Name string `db:"name"`
}
âÂÂ
// QueryByIDs æ¥询 ID å¨æÂÂå®ÂéÂÂÃ¥ÂÂä¸ÂçÂÂæ°æ®
func QueryByIDs(ids []int) (users []user, err error) {
// In å±Âå¼Âargsä¸ÂçÂÂÃ¥ÂÂçÂÂå¼ï¼Âè¿ÂÃ¥ÂÂä¿®æ¹åÂÂçÂÂæ¥询åÂÂ符串åÂÂä¸Â个å¯以ç±æ°æ®åºÂæ§è¡ÂçÂÂæ°çÂÂargÃ¥ÂÂ表ãÂÂ
// âÂÂæ¥询âÂÂåºÂ该使ç¨âÂÂ?âÂÂâÂÂbindVarãÂÂè¿ÂÃ¥ÂÂå¼使ç¨' ?âÂÂbindVar
query, args, err := sqlx.In("SELECT name, age FROM user WHERE id IN (?)", ids)
if err != nil {
return nil, err
}
// Rebind å°Âæ¥询仠QUESTION 转æ¢为DB驱å¨ç¨ÂåºÂç bindvar ç±»åÂÂãÂÂ
query = db.Rebind(query)
// Select 使ç¨æ¤æ°æ®åºÂãÂÂä»»ä½Âå ä½Â符åÂÂæ°é½å°Â被æÂÂä¾ÂçÂÂÃ¥ÂÂæ°æ¿æ¢ãÂÂ
err = db.Select(&users, query, args...)
if err != nil {
return nil, err
}
return users, nil
}
âÂÂ
// QueryAndOrderByIDs æ ¹æ® ID å¨æÂÂå®ÂéÂÂÃ¥ÂÂä¸ÂÃ¥ÂÂæÂÂå®Â顺åºÂæ¥询
func QueryAndOrderByIDs(ids []int) (users []user, err error) {
// Ã¥ÂÂ建ä¸Â个åÂÂ符串åÂÂçÂÂï¼Â大å°Â为idsçÂÂé¿度
strIDs := make([]string, 0, len(ids))
// å°Âids转æ¢为åÂÂ符串类åÂÂ
for _, id := range ids {
// Sprintfæ ¹æ®格å¼Â说æÂÂ符è¿Âè¡Âæ ¼å¼ÂÃ¥ÂÂï¼Â并è¿ÂÃ¥ÂÂç»ÂæÂÂÃ¥ÂÂ符串ãÂÂ
strIDs = append(strIDs, fmt.Sprintf("%d", id))
}
// Inå±Âå¼Âargsä¸ÂçÂÂÃ¥ÂÂçÂÂå¼ï¼Âè¿ÂÃ¥ÂÂä¿®æ¹åÂÂçÂÂæ¥询åÂÂ符串åÂÂä¸Â个å¯以ç±æ°æ®åºÂæ§è¡ÂçÂÂæ°çÂÂargÃ¥ÂÂ表ãÂÂâÂÂæ¥询âÂÂåºÂ该使ç¨âÂÂ?âÂÂâÂÂbindVarãÂÂè¿ÂÃ¥ÂÂå¼使ç¨' ?âÂÂbindVarãÂÂ
query, args, err := sqlx.In("SELECT name, age FROM user WHERE id IN (?) ORDER BY FIND_IN_SET(id, ?)", ids, strings.Join(strIDs, ","))
if err != nil {
return
}
âÂÂ
// Rebind å°Âæ¥询ä»ÂQUESTION转æ¢为DB驱å¨ç¨ÂåºÂçÂÂbindvarç±»åÂÂãÂÂ
query = db.Rebind(query)
// æ§è¡Âæ¥询 Select 使ç¨æ¤æ°æ®åºÂãÂÂä»»ä½Âå ä½Â符åÂÂæ°é½å°Â被æÂÂä¾ÂçÂÂÃ¥ÂÂæ°æ¿æ¢ãÂÂ
err = db.Select(&users, query, args...)
return
}
âÂÂ
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// IN æ¥询
users, err := QueryByIDs([]int{1, 15, 21, 2})
if err != nil {
fmt.Printf("query error: %v\n", err)
return
}
fmt.Printf("query successful result users %v\n", users)
for _, user := range users {
fmt.Printf("user: %#v\n", user)
}
âÂÂ
fmt.Println("**************")
// FIND_IN_SET
users, err = QueryAndOrderByIDs([]int{1, 15, 21, 2})
if err != nil {
fmt.Printf("query error: %v\n", err)
return
}
fmt.Printf("query successful result users %v\n", users)
for _, user := range users {
fmt.Printf("user: %#v\n", user)
}
}
âÂÂ
è¿Âè¡Â
Code/go/sqlx_demo via ð¹ v1.20.3 via ðÂÂ
 base
â go run main.go
init DB succeeded
query successful result users [{0 12 å°Âä¹Â} {0 22 å°Âä¹Â} {0 25 Ã¥ÂÂå¤Â} {0 16 è¤Âå§Â}]
user: main.user{ID:0, Age:12, Name:"å°Âä¹Â"}
user: main.user{ID:0, Age:22, Name:"å°Âä¹Â"}
user: main.user{ID:0, Age:25, Name:"Ã¥ÂÂå¤Â"}
user: main.user{ID:0, Age:16, Name:"è¤Âå§Â"}
**************
query successful result users [{0 12 å°Âä¹Â} {0 25 Ã¥ÂÂå¤Â} {0 16 è¤Âå§Â} {0 22 å°Âä¹Â}]
user: main.user{ID:0, Age:12, Name:"å°Âä¹Â"} à# FIND_IN_SET æÂÂçÂ
§æÂÂå®Â顺åºÂæ¥询
user: main.user{ID:0, Age:25, Name:"Ã¥ÂÂå¤Â"}
user: main.user{ID:0, Age:16, Name:"è¤Âå§Â"}
user: main.user{ID:0, Age:22, Name:"å°Âä¹Â"}
âÂÂ
Code/go/sqlx_demo via ð¹ v1.20.3 via ðÂÂ
 base
âÂÂ
âÂÂ
注æÂÂï¼Âå¼ÂÃ¥ÂÂä¸Âï¼Â使ç¨代ç ÂæÂÂåºÂè¿Âæ¯使ç¨ SQL FIND_IN_SET æ¥询æÂÂåºÂï¼ÂéÂÂè¦Âæ ¹æ®å¼ÂÃ¥ÂÂå®Âé æ åµæ¥使ç¨ãÂÂ
å®Âæ¹示ä¾Â
package main
âÂÂ
import (
ÃÂ ÃÂ "database/sql"
ÃÂ ÃÂ "fmt"
ÃÂ ÃÂ "log"
ÃÂ ÃÂ
ÃÂ ÃÂ _ "github.com/lib/pq"
ÃÂ ÃÂ "github.com/jmoiron/sqlx"
)
âÂÂ
var schema = `
CREATE TABLE person (
ÃÂ ÃÂ first_name text,
ÃÂ ÃÂ last_name text,
ÃÂ ÃÂ email text
);
âÂÂ
CREATE TABLE place (
ÃÂ ÃÂ country text,
ÃÂ ÃÂ city text NULL,
ÃÂ ÃÂ telcode integer
)`
âÂÂ
type Person struct {
ÃÂ ÃÂ FirstName string `db:"first_name"`
ÃÂ ÃÂ LastName ÃÂ string `db:"last_name"`
ÃÂ ÃÂ Email ÃÂ ÃÂ string
}
âÂÂ
type Place struct {
ÃÂ ÃÂ Country string
ÃÂ ÃÂ City ÃÂ ÃÂ sql.NullString
ÃÂ ÃÂ TelCode int
}
âÂÂ
func main() {
ÃÂ ÃÂ // this Pings the database trying to connect
ÃÂ ÃÂ // use sqlx.Open() for sql.Open() semantics
ÃÂ ÃÂ db, err := sqlx.Connect("postgres", "user=foo dbname=bar sslmode=disable")
ÃÂ ÃÂ if err != nil {
ÃÂ ÃÂ ÃÂ ÃÂ log.Fatalln(err)
ÃÂ }
âÂÂ
ÃÂ ÃÂ // exec the schema or fail; multi-statement Exec behavior varies between
ÃÂ ÃÂ // database drivers; pq will exec them all, sqlite3 won't, ymmv
ÃÂ ÃÂ db.MustExec(schema)
ÃÂ ÃÂ
ÃÂ ÃÂ tx := db.MustBegin()
ÃÂ ÃÂ tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "Jason", "Moiron", "jmoiron@jmoiron.net")
ÃÂ ÃÂ tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "John", "Doe", "johndoeDNE@gmail.net")
ÃÂ ÃÂ tx.MustExec("INSERT INTO place (country, city, telcode) VALUES ($1, $2, $3)", "United States", "New York", "1")
ÃÂ ÃÂ tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Hong Kong", "852")
ÃÂ ÃÂ tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Singapore", "65")
ÃÂ ÃÂ // Named queries can use structs, so if you have an existing struct (i.e. person := &Person{}) that you have populated, you can pass it in as &person
ÃÂ ÃÂ tx.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", &Person{"Jane", "Citizen", "jane.citzen@example.com"})
ÃÂ ÃÂ tx.Commit()
âÂÂ
ÃÂ ÃÂ // Query the database, storing results in a []Person (wrapped in []interface{})
ÃÂ ÃÂ people := []Person{}
ÃÂ ÃÂ db.Select(&people, "SELECT * FROM person ORDER BY first_name ASC")
ÃÂ ÃÂ jason, john := people[0], people[1]
âÂÂ
ÃÂ ÃÂ fmt.Printf("%#v\n%#v", jason, john)
ÃÂ ÃÂ // Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
ÃÂ ÃÂ // Person{FirstName:"John", LastName:"Doe", Email:"johndoeDNE@gmail.net"}
âÂÂ
ÃÂ ÃÂ // You can also get a single result, a la QueryRow
ÃÂ ÃÂ jason = Person{}
ÃÂ ÃÂ err = db.Get(&jason, "SELECT * FROM person WHERE first_name=$1", "Jason")
ÃÂ ÃÂ fmt.Printf("%#v\n", jason)
ÃÂ ÃÂ // Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
âÂÂ
ÃÂ ÃÂ // if you have null fields and use SELECT *, you must use sql.Null* in your struct
ÃÂ ÃÂ places := []Place{}
ÃÂ ÃÂ err = db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC")
ÃÂ ÃÂ if err != nil {
ÃÂ ÃÂ ÃÂ ÃÂ fmt.Println(err)
ÃÂ ÃÂ ÃÂ ÃÂ return
ÃÂ }
ÃÂ ÃÂ usa, singsing, honkers := places[0], places[1], places[2]
ÃÂ ÃÂ
ÃÂ ÃÂ fmt.Printf("%#v\n%#v\n%#v\n", usa, singsing, honkers)
ÃÂ ÃÂ // Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
ÃÂ ÃÂ // Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
ÃÂ ÃÂ // Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
âÂÂ
ÃÂ ÃÂ // Loop through rows using only one struct
ÃÂ ÃÂ place := Place{}
ÃÂ ÃÂ rows, err := db.Queryx("SELECT * FROM place")
ÃÂ ÃÂ for rows.Next() {
ÃÂ ÃÂ ÃÂ ÃÂ err := rows.StructScan(&place)
ÃÂ ÃÂ ÃÂ ÃÂ if err != nil {
ÃÂ ÃÂ ÃÂ ÃÂ ÃÂ ÃÂ log.Fatalln(err)
ÃÂ ÃÂ ÃÂ }
ÃÂ ÃÂ ÃÂ ÃÂ fmt.Printf("%#v\n", place)
ÃÂ }
ÃÂ ÃÂ // Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
ÃÂ ÃÂ // Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
ÃÂ ÃÂ // Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
âÂÂ
ÃÂ ÃÂ // Named queries, using `:name` as the bindvar. Automatic bindvar support
ÃÂ ÃÂ // which takes into account the dbtype based on the driverName on sqlx.Open/Connect
ÃÂ ÃÂ _, err = db.NamedExec(`INSERT INTO person (first_name,last_name,email) VALUES (:first,:last,:email)`,
ÃÂ ÃÂ ÃÂ ÃÂ map[string]interface{}{
ÃÂ ÃÂ ÃÂ ÃÂ ÃÂ ÃÂ "first": "Bin",
ÃÂ ÃÂ ÃÂ ÃÂ ÃÂ ÃÂ "last": "Smuth",
ÃÂ ÃÂ ÃÂ ÃÂ ÃÂ ÃÂ "email": "bensmith@allblacks.nz",
ÃÂ })
âÂÂ
ÃÂ ÃÂ // Selects Mr. Smith from the database
ÃÂ ÃÂ rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:fn`, map[string]interface{}{"fn": "Bin"})
âÂÂ
ÃÂ ÃÂ // Named queries can also use structs. Their bind names follow the same rules
ÃÂ ÃÂ // as the name -> db mapping, so struct fields are lowercased and the `db` tag
ÃÂ ÃÂ // is taken into consideration.
ÃÂ ÃÂ rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason)
ÃÂ ÃÂ
ÃÂ ÃÂ
ÃÂ ÃÂ // batch insert
ÃÂ ÃÂ
ÃÂ ÃÂ // batch insert with structs
ÃÂ ÃÂ personStructs := []Person{
ÃÂ ÃÂ ÃÂ {FirstName: "Ardie", LastName: "Savea", Email: "asavea@ab.co.nz"},
ÃÂ ÃÂ ÃÂ {FirstName: "Sonny Bill", LastName: "Williams", Email: "sbw@ab.co.nz"},
ÃÂ ÃÂ ÃÂ {FirstName: "Ngani", LastName: "Laumape", Email: "nlaumape@ab.co.nz"},
ÃÂ }
âÂÂ
ÃÂ ÃÂ _, err = db.NamedExec(`INSERT INTO person (first_name, last_name, email)
ÃÂ ÃÂ ÃÂ ÃÂ VALUES (:first_name, :last_name, :email)`, personStructs)
âÂÂ
ÃÂ ÃÂ // batch insert with maps
ÃÂ ÃÂ personMaps := []map[string]interface{}{
ÃÂ ÃÂ ÃÂ {"first_name": "Ardie", "last_name": "Savea", "email": "asavea@ab.co.nz"},
ÃÂ ÃÂ ÃÂ {"first_name": "Sonny Bill", "last_name": "Williams", "email": "sbw@ab.co.nz"},
ÃÂ ÃÂ ÃÂ {"first_name": "Ngani", "last_name": "Laumape", "email": "nlaumape@ab.co.nz"},
ÃÂ }
âÂÂ
ÃÂ ÃÂ _, err = db.NamedExec(`INSERT INTO person (first_name, last_name, email)
ÃÂ ÃÂ ÃÂ ÃÂ VALUES (:first_name, :last_name, :email)`, personMaps)
}