查询数据

执行返回数据的 SQL 语句时,请使用database/sql包中提供的Query方法之一。其中每个都返回一个RowRows ,您可以使用 Scan 方法将其数据复制到变量。例如,您可以使用这些方法来执行 SELECT 语句。

执行不返回数据的语句时,可以使用 ExecExecContext方法。有关更多信息,请参阅 执行不返回数据的语句

database/sql包提供了两种执行结果查询的方法。

如果您的代码将重复执行相同的 SQL 语句,请考虑使用预准备语句。有关详细信息,请参阅使用预准备语句

警告: 不要使用字符串格式化函数,例如fmt.Sprintf组装 SQL 语句!您可能会引入 SQL 注入风险。有关更多信息,请参阅避免 SQL 注入风险

查询单行

QueryRow 最多检索单个数据库行,例如当您要通过唯一 ID 查找数据时。如果查询返回多行,则 Scan 方法将丢弃除第一行之外的所有行。

QueryRowContext 的工作方式类似于QueryRow,但有一个 context.Context 参数。有关详细信息,请参阅取消正在进行的操作

下面的示例使用查询来查明是否有足够的库存来支持购买。如果有足够的,则 SQL 语句返回 true;如果没有足够的,则返回falseRow.Scan 通过指针将布尔返回值复制到enough变量中。

func canPurchase(id int, quantity int) (bool, error) {
    var enough bool
    // 基于单行查询值.
    if err := db.QueryRow("SELECT (quantity >= ?) from album where id = ?",
        quantity, id).Scan(&enough); err != nil {
        if err == sql.ErrNoRows {
            return false, fmt.Errorf("canPurchase %d: unknown album", id)
        }
        return false, fmt.Errorf("canPurchase %d: %v", id)
    }
    return enough, nil
}

注意: 预准备语句中的参数占位符因您使用的 DBMS 和驱动程序而异。例如,Postgres 的 pq 驱动需要一个占位符,例如是$1而不是 ?

处理错误

QueryRow本身不返回任何错误。相反,Scan 会报告组合查找和扫描中的任何错误。当查询没有找到任何行时,它会返回 sql.ErrNoRows

返回单行的函数

函数 描述
DB.QueryRow
DB.QueryRowContext
单独运行单行查询.
Tx.QueryRow
Tx.QueryRowContext
在更大的事务中运行单行查询。有关更多信息,请参阅 执行事务.
Stmt.QueryRow
Stmt.QueryRowContext
使用已准备好的语句运行单行查询。有关详细信息,请参阅使用预准备语句.
Conn.QueryRowContext 用于保留连接。有关更多信息,请参阅 管理连接.

查询多行

可以使用QueryQueryContext查询多行,它们返回表示查询结果的Rows。您的代码使用 Rows.Next循环访问返回的行。每次迭代都调用 Scan 以将列值复制到变量中。

QueryContext的工作方式与Query 类似,但具有context.Context参数。有关详细信息,请参阅取消正在进行的操作

下面的示例执行一个查询以返回指定艺术家的专辑。专辑以一个 sql.Rows返回。该代码使用 Rows.Scan 将列值复制到由指针表示的变量中。

func albumsByArtist(artist string) ([]Album, error) {
    rows, err := db.Query("SELECT * FROM album WHERE artist = ?", artist)
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    // 用于保存返回行中的数据的专辑切片
    var albums []Album

    // 遍历行,使用Scan将列数据分配给结构字段。
    for rows.Next() {
        var alb Album
        if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist,
            &alb.Price, &alb.Quantity); err != nil {
            return albums, err
        }
        albums = append(albums, album)
    }
    if err = rows.Err(); err != nil {
        return albums, err
    }
    return albums, nil
}

请注意对rows.Close的延迟调用。这将释放行持有的任何资源,无论函数如何返回。在行中一直循环也会隐式关闭它,但最好使用 defer 来确保无论如何都关闭rows

注意: 预准备语句中的参数占位符因您使用的 DBMS 和驱动程序而异。例如,Postgres 的 pq 驱动 需要一个占位符,例如是$1而不是 ?

处理错误

确保 sql.Rows在循环查询结果后检查错误。如果查询失败,这就是您的代码发现的方式。

返回多行的函数

函数 描述
DB.Query
DB.QueryContext
单独运行查询.
Tx.Query
Tx.QueryContext
在更大的事务中运行查询。有关更多信息,请参阅 执行事务.
Stmt.Query
Stmt.QueryContext
使用已准备好的语句运行查询。有关详细信息,请参阅 使用预准备语句.
Conn.QueryContext 用于保留连接。有关更多信息,请参阅 管理连接.

处理可为null的列值

database/sql包提供了几种特殊的类型,当列的值可能为 null 时,可以用作 Scan 函数的参数。每个都包括一个Valid字段,用于报告该值是否为非 null,以及一个保存该值(如果是这样)的字段.

以下示例中的代码查询客户名称。如果 name 值为 null,则代码将替换另一个值以在应用程序中使用。

var s sql.NullString
err := db.QueryRow("SELECT name FROM customer WHERE id = ?", id).Scan(&s)
if err != nil {
    log.Fatal(err)
}

// 查找客户名称,如果不存在,请使用占位符.
name := "Valued Customer"
if s.Valid {
    name = s.String
}

sql 包参考中查看有关每种类型的详细信息:

从列中获取数据

循环查询返回的行时,您可以使用Scan将行的列值复制到 Go 值中,如Rows.Scan参考中所述。

所有驱动程序都支持一组基本的数据转换,例如将SQL INT 转换为 Go int。一些驱动程序扩展了这组转换;有关详细信息,请参阅每个驱动程序的文档。

如您所料, Scan将从列类型转换为类似的 Go 类型。例如, Scan 将从 SQL CHAR, VARCHARTEXT 转换为 Go string。但是,Scan 还会执行到适合列值的另一种 Go 类型的转换。例如,如果列是始终包含数字的 VARCHAR,则可以指定数字 Go 类型(如 int)来接收该值, Scan 将为你使用 strconv.Atoi 对其进行转换。

有关Scan函数进行的转换的更多详细信息,请参阅Rows.Scan参考资料。

处理多个结果集

当您的数据库操作可能返回多个结果集时,您可以使用 Rows.NextResultSet检索这些结果集. 例如,当您发送单独查询多个表的 SQL 并为每个表返回一个结果集时,这可能很有用。

Rows.NextResultSet准备下一个结果集,以便调用 Rows.Next 从下一个结果集中检索第一行。它返回一个布尔值,指示是否有下一个结果集

以下示例中的代码使用 DB.Query以执行两个 SQL 语句。第一个结果集来自过程中的第一个查询,检索album表中的所有行。下一个结果集来自第二个查询,从song表中检索行.

rows, err := db.Query("SELECT * from album; SELECT * from song;")
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

// 循环遍历第一个结果集.
for rows.Next() {
    // 处理结果集.
}

// 前进到下一个结果集.
rows.NextResultSet()

// 循环遍历第二个结果集.
for rows.Next() {
    // Handle second set.
}

// 检查任一结果集中是否存在任何错误.
if err := rows.Err(); err != nil {
    log.Fatal(err)
}