本文翻译自《Querying for data》。
目录
查询单行
查询多行
处理可null列的值
从列中获取数据
处理多个结果集
执行返回行数据的SQL语句时,请使用database/sql
包中提供的Query
方法之一。其中每一个都返回一行或多行,你可以使用Scan
方法将其数据复制到变量中。例如,你可以使用这些方法来执行SELECT
语句。
执行不返回行数据的语句,可以改用Exec
或ExecContext
方法。有关详细信息,请参阅执行不返回数据的SQL语句。
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
// 查询基于单行的值。
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
这样的占位符,而MySQL驱动程序使用?
号。
处理错误
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
参数。有关更多信息,请参阅取消正在进行中的数据库操作。
以下示例执行查询以返回指定艺术家(artist
)的专辑(album
)信息。album
在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方法把每一行的各列的值,赋值给一个结构体变量的对应字段。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, album)
}
if err = rows.Err(); err != nil {
return albums, err
}
return albums, nil
}
请注意对rows.Close
函数的延迟调用。无论函数如何返回,这都会释放sql.Rows
占有的所有资源。虽然循环遍历完毕所有的行也会隐式关闭它,但最好使用defer
来确保无论如何都能关闭。
注意:预处理语句中的参数占位符因所使用的DBMS和驱动程序而异。例如,Postgres的pq驱动程序需要像$1
这样的占位符,而MySQL驱动程序使用?
号。
处理错误
请务必在遍历查询结果后检查sql.Rows
中是否有错误发生。如果查询失败,这就是你的代码发现错误的方式。
以下列出返回多行数据的函数
DB.Query
DB.QueryContext
单独运行查询。
Tx.Query
Tx.QueryContext
在较大的事务中执行查询。有关更多信息,请参阅执行事务。
Stmt.Query
Stmt.QueryContext
使用一个准备好的语句运行查询。有关更多信息,请参阅使用准备好的语句。
Conn.QueryContext
与专用的数据库连接一起使用。有关更多信息,请参阅管理数据库连接。
处理可null列的值
当列的值可能为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)
}
name := "Valued Customer"
// 如果客户名称不为null(合法,s.Valid的值为true),那么赋值给name变量
if s.Valid {
name = s.String
}
请参阅sql包关于每个特殊类型的更多信息:
从列中获取数据
在查询返回的行上循环遍历时,可以使用Scan
函数将行的列值复制到Go值中,例如rows.Scan
的参考文档中所述。
所有驱动程序都支持一组基本的数据类型转换,例如将SQL的INT
转换为Go的INT
。一些驱动程序扩展了这组转换;有关详细信息,请参阅每个驱动程序的文档。
正如你所料,Scan
函数将把列类型转换为类似的Go类型。例如,Scan
将把SQL的CHAR
、VARCHAR
和TEXT
转换为Go字符串。但是,Scan
还可以把列值转换为另一个非常适合的Go类型。例如,如果列是始终包含数字的VARCHAR
,则可以指定数字Go类型(例如int
)来接收值,Scan将使用strconv.Atoi
函数进行转换。
有关Scan
函数进行类型转换的更多详细信息,请参阅Rows.Scan
参考文档。
处理多个结果集
当数据库操作可能返回多个结果集时,你可以使用Rows.NextResultSet
函数来检索这些结果集。例如,当你发送分别查询多个表的一个SQL语句,并为其中每个表都返回一个结果集时,这可能很有用。
Rows.NextResultSet
函数会准备下一个结果集,以便调用Rows.Next
函数从下一个集合中检索第一行。它返回一个布尔值,指示是否存在下一个结果集。
以下示例中的代码使用DB.Query
函数来执行两条Select
语句。第一个结果集来自第一个Select
查询,检索相簿album
表中的所有行。下一个结果集来自第二个Select
查询,从歌曲表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() {
// 处理结果集…
}
// 检查在处理所有结果集的过程中,是否有错误发生。
if err := rows.Err(); err != nil {
log.Fatal(err)
}