如何使用pgx将QueryRow扫描到结构体中

18

我们正在使用一个具有许多字段的用户结构,如下:

type user struct {
    ID       int    `json:"id,omitempty"`
    UUID     string `json:"uuid,omitempty"`
    Role     int    `json:"role,omitempty"`
    Name     string `json:"name,omitempty"`
    Surname  string `json:"surname,omitempty"`
    Phone    string `json:"phone,omitempty"`
    Email    string `json:"email,omitempty"`
    Street   string `json:"street,omitempty"`
    City     string `json:"city,omitempty"`
    Password string `json:"password,omitempty"`
}

并且有一个通过电子邮件获取用户的函数:

func getUserByEmail(email string) (u user, err error) {
    row := db.Psql.QueryRow(
        context.Background(),
        "SELECT * FROM users WHERE email=$1",
        email)
    err = row.Scan(&u.ID, &u.UUID, &u.Role, &u.Name, &u.Surname, &u.Phone, &u.Email, &u.Street, &u.City, &u.Password)
    if err != nil {
        log.Fatal(err)
    }
    return
}

有没有一种方法可以直接扫描到一个结构体,而不是它的所有属性?理想情况下:

row.Scan(&u)
4个回答

28

还有另一个库scany。 它可以与本机接口 pgxdatabase/sql一起使用:

package main

import (
    "context"

    "github.com/jackc/pgx/v4/pgxpool"

    "github.com/georgysavva/scany/pgxscan"
)

type User struct {
    ID    string
    Name  string
    Email string
    Age   int
}

func main() {
    ctx := context.Background()
    db, _ := pgxpool.Connect(ctx, "example-connection-url")

    var users []*User
    pgxscan.Select(ctx, db, &users, `SELECT id, name, email, age FROM users`)
    // users variable now contains data from all rows.
}

这个库经过充分的测试和文档记录,相比于sqlx有更少的概念。

免责声明,我是这个库的作者。


1
请问您能展示一下如何使用scany来进行查询参数吗?我在库中搜索了一下,但是没有找到关于如何使用查询参数的例子;否则查询可能会受到SQL注入攻击的影响。 - Monty
@Monty 你好。使用scany处理查询参数的方式与直接使用数据库库处理它们的方式相同。你可以自己从数据库库中查询数据库行,然后scany完全避开SQL处理和查询参数。或者你可以使用.Select().Get() scany函数之一,并正常传递参数,scany将把它们传递给底层的数据库库,负责转义它们,以防止注入攻击。 - Georgy Savva
这个答案已过时;自从 PGX 5.1.0 版本起,PGX 库已支持结构体扫描。请参阅 https://dev59.com/AFIH5IYBdhLWcg3wA3x5#76923598 - Niels Krijger

3
不是用普通的 database/sql,而是有一个扩展库叫做sqlx,它在 database/sql 的基础上添加了一些有用的扩展功能,比如将行解析到结构体中(包括嵌套的)、切片和数组中。
type Place struct {
    Country       string
    City          sql.NullString
    TelephoneCode int `db:"telcode"`
}

rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
    var p Place
    err = rows.StructScan(&p)
}

请查看文档,并查找StructScan


3
自从5.1.0版本起,PGX支持结构体扫描。
type product struct {
    ID    int32
    Name  string
    Price int32
}

rows, _ := conn.Query(ctx, "select * from products")
products, err := pgx.CollectRows(rows, pgx.RowToStructByName[product])
if err != nil {
    fmt.Printf("CollectRows error: %v", err)
    return
}

for _, p := range products {
    fmt.Printf("%s: $%d\n", p.Name, p.Price)
}

您可以使用 db 标签为字段命名。
type Name struct {
    Last  string `db:"last_name"`
    First string `db:"first_name"`
}

文档:https://pkg.go.dev/github.com/jackc/pgx/v5#RowToStructByName


0
如果您是通过 Google 进入这里,而不想使用 struct,并希望直接得到 map[string]interface{} 类型的输出,那么您可以尝试以下方法:
query := `SELECT * FROM product WHERE id = @id`
bindVars := make(map[string]interface{})
bindVars["id"] = id

/*
    with pgx.NamedArgs
    @link https://github.com/jackc/pgx/issues/387#issuecomment-1107666716
*/
jsonbody, err := json.Marshal(bindVars)
if err != nil {
    return nil, err
}
var namedArgs pgx.NamedArgs
if err := json.Unmarshal(jsonbody, &namedArgs); err != nil {
    return nil, err
}
jsonSQL := fmt.Sprintf("SELECT row_to_json(t) FROM (%s) t", query)
var data interface{}
err = conn.QueryRow(context.Background(), jsonSQL, namedArgs).Scan(&data)
if err != nil {
    return nil, err
}
if data == nil {
    return nil, errors.New("no document was found")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接