查询数据
执行返回数据的 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.QueryRow DB.QueryRowContext
|
单独运行单行查询. |
Tx.QueryRow Tx.QueryRowContext
|
在更大的事务中运行单行查询。有关更多信息,请参阅 执行事务. |
Stmt.QueryRow Stmt.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.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
, 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)
}