数据查询
当执行返回数据的 SQL 语句时,请使用 database/sql
包中提供的 Query
方法之一。这些方法中的每一个都返回一个 Row
或 Rows
,您可以使用 Scan
方法将其数据复制到变量中。例如,您可以使用这些方法执行 SELECT
语句。
当执行不返回数据的语句时,您可以改用 Exec
或 ExecContext
方法。有关更多信息,请参阅 执行不返回数据的语句。
database/sql
包提供了两种方法来执行查询以获取结果。
- 查询单个行 –
QueryRow
最多从数据库返回单个Row
。有关更多信息,请参阅 查询单个行。 - 查询多行 –
Query
将所有匹配的行作为Rows
结构返回,您的代码可以循环遍历该结构。有关更多信息,请参阅 查询多行。
如果您的代码将重复执行相同的 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
// Query for a value based on a single row.
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, err)
}
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()
// An album slice to hold data from returned rows.
var albums []Album
// Loop through rows, using Scan to assign column data to struct fields.
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
的延迟调用。无论函数如何返回,这都会释放行持有的任何资源。循环遍历所有行也会隐式地关闭它,但最好使用 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
包提供了一些特殊的类型,您可以将这些类型用作 Scan
函数的参数。每个类型都包含一个 Valid
字段,用于报告值是否为非 null,以及一个字段,如果为非 null 则保存该值。
以下示例中的代码查询客户名称。如果名称值为 null,则代码会替换另一个值以供应用程序使用。
var s sql.NullString
err := db.QueryRow("SELECT name FROM customer WHERE id = ?", id).Scan(&s)
if err != nil {
log.Fatal(err)
}
// Find customer name, using placeholder if not present.
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()
// Loop through the first result set.
for rows.Next() {
// Handle result set.
}
// Advance to next result set.
rows.NextResultSet()
// Loop through the second result set.
for rows.Next() {
// Handle second set.
}
// Check for any error in either result set.
if err := rows.Err(); err != nil {
log.Fatal(err)
}