查询数据
当执行返回数据的 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,则代码会替换为另一个值以供应用程序使用。
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)
}