查询数据
执行返回数据的 SQL 语句时,请使用database/sql包中提供的Query方法之一。其中每个都返回一个Row
或 Rows ,您可以使用 Scan 方法将其数据复制到变量。例如,您可以使用这些方法来执行 SELECT 语句。
执行不返回数据的语句时,可以使用 Exec 或
ExecContext方法。有关更多信息,请参阅 执行不返回数据的语句。
database/sql包提供了两种执行结果查询的方法。
如果您的代码将重复执行相同的 SQL 语句,请考虑使用预准备语句。有关详细信息,请参阅使用预准备语句。
警告: 不要使用字符串格式化函数,例如fmt.Sprintf组装 SQL 语句!您可能会引入 SQL 注入风险。有关更多信息,请参阅避免 SQL 注入风险。
查询单行
QueryRow 最多检索单个数据库行,例如当您要通过唯一 ID 查找数据时。如果查询返回多行,则 Scan 方法将丢弃除第一行之外的所有行。
QueryRowContext 的工作方式类似于QueryRow,但有一个 context.Context 参数。有关详细信息,请参阅取消正在进行的操作。
下面的示例使用查询来查明是否有足够的库存来支持购买。如果有足够的,则 SQL 语句返回 true;如果没有足够的,则返回false。Row.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.QueryRowDB.QueryRowContext
|
单独运行单行查询. |
Tx.QueryRowTx.QueryRowContext
|
在更大的事务中运行单行查询。有关更多信息,请参阅 执行事务. |
Stmt.QueryRowStmt.QueryRowContext
|
使用已准备好的语句运行单行查询。有关详细信息,请参阅使用预准备语句. |
Conn.QueryRowContext
|
用于保留连接。有关更多信息,请参阅 管理连接. |
查询多行
可以使用Query 或 QueryContext查询多行,它们返回表示查询结果的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.QueryDB.QueryContext
|
单独运行查询. |
Tx.QueryTx.QueryContext
|
在更大的事务中运行查询。有关更多信息,请参阅 执行事务. |
Stmt.QueryStmt.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, VARCHAR 和 TEXT 转换为 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)
}