数据查询
当执行返回数据的SQL语句时,请使用database/sql包中提供的Query方法之一。这些方法会返回Row或Rows对象,您可以通过Scan方法将数据复制到变量中。例如,这些方法可用于执行SELECT语句。
当执行不返回数据的语句时,可以改用Exec或ExecContext方法。更多信息请参阅执行不返回数据的语句。
database/sql包提供了两种执行查询以获取结果的方式:
- 查询单行数据 –
QueryRow最多从数据库返回单行数据。更多信息请参阅查询单行数据。 - 查询多行数据 –
Query将所有匹配的行作为Rows结构体返回,您的代码可对其循环遍历。更多信息请参阅查询多行数据。
如果您的代码需要重复执行相同的SQL语句,请考虑使用预处理语句。更多信息请参阅使用预处理语句。
注意: 不要使用fmt.Sprintf等字符串格式化函数来拼接SQL语句!这可能导致SQL注入风险。更多信息请参阅避免SQL注入风险。
查询单行数据
QueryRow最多检索单行数据库数据,例如当您需要通过唯一ID查找数据时。如果查询返回多行结果,Scan方法将仅保留第一行结果,其余数据将被丢弃。
QueryRowContext功能与QueryRow类似,但额外接受context.Context参数。更多信息请参阅取消正在进行的操作。
以下示例使用查询来判断库存是否足以支持购买。SQL语句在库存充足时返回true,不足时返回false。Row.Scan通过指针将布尔返回值复制到enough变量中。go 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: 未知专辑", id) } return false, fmt.Errorf("canPurchase %d: %v", id, err) } return enough, nil } 注意: 预编译语句中的参数占位符会因您使用的数据库管理系统和驱动程序而异。例如,PostgreSQL 的 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 将列值复制到由指针表示的变量中。```go
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, alb)
}
if err = rows.Err(); err != nil {
return albums, err
}
return albums, nil
}
```请注意这里使用了延迟调用 rows.Close。无论函数以何种方式返回,这都会释放rows所持有的任何资源。遍历完所有行也会隐式关闭rows,但使用defer能确保无论发生什么情况rows都会被关闭。
注意: 预置语句中的参数占位符因使用的数据库管理系统和驱动而异。例如,PostgreSQL的 pq驱动 需要使用类似 $1 的占位符,而不是 ?。
处理错误
在循环遍历查询结果后,请务必检查 sql.Rows 是否返回错误。如果查询失败,这是代码检测错误的方式。
返回多行的函数
| 函数 | 描述 |
|---|---|
DB.QueryDB.QueryContext
|
在独立会话中运行查询。 |
Tx.QueryTx.QueryContext
|
在更大的事务中运行查询。更多信息请参阅执行事务。 |
Stmt.QueryStmt.QueryContext
|
使用已准备好的语句运行查询。更多信息请参阅使用预置语句。 |
Conn.QueryContext
|
用于预留连接。更多信息请参阅管理连接。 |
处理可空列值
当列值可能为null时,database/sql 包提供了几种特殊类型,可以作为 Scan 函数的参数使用。每个类型都包含一个 Valid 字段(用于报告值是否非空),以及一个在值非空时存储该值的字段。
以下示例中的代码查询客户姓名。如果姓名值为null,代码会替换另一个值供应用程序使用。```go var s sql.NullString err := db.QueryRow(“SELECT name FROM customer WHERE id = ?”, id).Scan(&s) if err != nil { log.Fatal(err) }
// 获取客户姓名,如果为空则使用占位值。
name := “尊敬的客户”
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 表中的行。```go
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() { // 处理第二个结果集。 }
// 检查结果集中的任何错误。 if err := rows.Err(); err != nil { log.Fatal(err) }