查询数据

本文翻译自《Querying for data》。

目录

查询单行

查询多行

处理可null列的值

从列中获取数据

处理多个结果集

执行返回行数据的SQL语句时,请使用database/sql包中提供的Query方法之一。其中每一个都返回一行或多行,你可以使用Scan方法将其数据复制到变量中。例如,你可以使用这些方法来执行SELECT语句。

执行不返回行数据的语句,可以改用ExecExecContext方法。有关详细信息,请参阅执行不返回数据的SQL语句

database/sql包提供了两种执行查询并返回结果的方法:

  • 查询单行QueryRow方法从数据库中最多返回一行作为一个Row结构体。有关详细信息,请参阅查询单行
  • 查询多行Query方法返回所有匹配的行作为代码可以循环的Rows结构体。有关详细信息,请参阅查询多行

如果你的代码将重复执行相同的SQL语句,请考虑使用准备好的语句(译者注:预处理语句)。有关详细信息,请参阅使用准备好的语句

注意:不要使用fmt.Sprintf等字符串拼接函数来组装SQL语句!否则你可能会引入SQL注入风险。有关更多信息,请参阅避免SQL注入风险

查询单行

QueryRow函数最多检索单个数据库行,例如通过唯一ID查找数据时。如果查询返回多行,则Scan方法会丢弃除第一行以外的所有行。

QueryRowContext函数的工作方式类似于QueryRow,但带有context.Context参数。有关更多信息,请参阅取消正在进行的数据库操作

以下示例使用查询来确定是否有足够的库存来支持购买。如果足够,则SQL语句返回true,否则返回falseRow.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

与专用的数据库连接一起使用。有关更多信息,请参阅管理数据库连接

查询多行

你可以使用QueryQueryContext函数查询多行,它们返回表示查询结果的Rows结构体。你的代码使用rows.Next方法对返回的行进行迭代。每次迭代都调用Scan方法将列值复制到变量中。

QueryContext函数的工作方式与Query类似,但带有context.Context参数。有关更多信息,请参阅取消正在进行中的数据库操作

以下示例执行查询以返回指定艺术家(artist)的专辑(album)信息。albumsql.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的CHARVARCHARTEXT转换为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)
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注