查询数据

Querying for data - 查询数据

原文:https://go.dev/doc/database/querying

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

​ 当执行不返回数据的语句时,可以使用ExecExecContext方法代替。更多信息,请参阅Executing statements that don’t return data(执行不返回数据的语句)

database/sql包提供了两种执行查询以获取结果的方法。

  • 查询单行 —— QueryRow最多从数据库返回一个Row。更多信息,请参阅Querying for a single row (查询单行)
  • 查询多行 —— Query将所有匹配的行作为Rows结构体返回,你的代码可以循环遍历它。更多信息,请参阅查询多行

​ 如果你的代码将重复执行相同的SQL语句,请考虑使用预处理语句。更多信息,请参阅Using prepared statements (使用预处理语句)

注意:不要使用字符串格式化函数(如fmt.Sprintf)来组装SQL语句!你可能会引入SQL注入风险。更多信息,请参阅避免SQL注入风险

查询单行

QueryRow检索最多一个数据库行,例如当你想通过唯一ID查找数据时。如果查询返回多个行,Scan方法将丢弃除第一个之外的所有行。

QueryRowContext的工作方式与QueryRow相同,但有一个context.Context参数。更多信息,请参阅Canceling in-progress operations(取消正在进行的操作)

​ 以下示例使用查询来确定是否有足够的库存来支持购买。如果有足够的库存,该SQL语句返回true,如果没有则返回falseRow.Scan通过指针将布尔返回值复制到enough变量中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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)
    }
    return enough, nil
}

注意:预处理语句中的参数占位符根据你使用的DBMS和驱动程序而异。例如,Postgrespq driver需要像$1这样的占位符,而不是?

处理错误

QueryRow本身不返回错误。相反,Scan报告组合查找和扫描的任何错误。当查询找不到任何行时,它返回sql.ErrNoRows

Functions for returning a single row 用于返回单行的函数

Function 函数Description 描述
DB.QueryRow DB.QueryRowContext单独运行一个单行查询。
Tx.QueryRow Tx.QueryRowContext在较大的事务中运行一个单行查询。更多信息,请参阅Executing transactions (执行事务)
Stmt.QueryRow Stmt.QueryRowContext使用预处理语句运行一个单行查询。更多信息,请参见 Using prepared statements(使用预处理语句)
Conn.QueryRowContext用于保留连接。更多信息,请参见Managing connections( 管理连接)

查询多行

​ 您可以使用QueryQueryContext查询多条记录,它们返回一个代表查询结果的Rows。您的代码使用Rows.Next对返回的行进行迭代。每次迭代都会调用Scan来将列值复制到变量中。

QueryContext的工作方式与Query类似,但有一个context.Context实参。更多信息请参见 Canceling in-progress operations (取消正在进行的操作)

​ 下面的例子执行了一个查询,返回指定艺术家的专辑。这些专辑被返回到一个sql.Rows中。该代码使用Rows.Scan将列值复制到由指针表示的变量中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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, album)
    }
    if err = rows.Err(); err != nil {
        return albums, err
    }
    return albums, nil
}

注意

注意:延迟调用的rows.Close。无论函数如何返回,这都会释放rows持有的任何资源。通过遍历所有行也会隐式地关闭它,但最好使用defer来确保无论发生什么情况,rows都会被关闭。

注意:预处理语句中的参数占位符根据你使用的DBMS和驱动程序而异。例如,Postgrespq driver需要一个类似于$1的占位符,而不是?

处理错误

Be sure to check for an error from sql.Rows after looping over query results. If the query failed, this is how your code finds out.

​ 在循环查询结果后,一定要检查sql.Rows的错误。如果查询失败,这就是你的代码如何发现的方式。

返回多行记录的函数

Function 函数Description 描述
DB.Query DB.QueryContext单独运行一个查询。
Tx.Query Tx.QueryContext在较大的事务中运行一个查询。更多信息,请参阅Executing transactions (执行事务)
Stmt.Query Stmt.QueryContext使用预处理语句运行一个查询。更多信息,请参见Using prepared statements(使用预处理语句)
Conn.QueryContext用于保留连接。更多信息,请参见Managing connections( 管理连接)

处理可为null的列值

database/sql包提供了几种特殊类型,你可以在Scan方法中使用它们作为参数,当列的值可能为null时。每个类型都包含一个Valid字段,报告值是否非null,以及一个字段(如果值为空),则持有该值。

​ 以下示例中的代码查询客户名称。如果名称值为null,则代码将另一个值替换为应用程序中使用的值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 CHARVARCHARTEXT转换为Go string。然而,Scan还将执行到另一个适合列值的Go类型的转换。例如,如果列始终包含一个数字的VARCHAR,你可以指定一个数值型的Go类型(如int)来接收该值,然后Scan将使用strconv.Atoi为你进行转换。

​ 有关Scan方法进行的转换的更多详细信息,请参阅Rows.Scan参考。

处理多个结果集

​ 当你的数据库操作可能返回多个结果集时,你可以使用Rows.NextResultSet来检索它们。例如,当你分别查询多个表并返回每个表的结果集时,这可能会很有用。

Rows.NextResultSet准备下一个结果集,以便对Rows.Next的调用可以从下一个集中检索第一行。它返回一个布尔值,表示是否确实存在下一个结果集。

​ 以下示例中的代码使用DB.Query执行两个SQL语句。第一个结果集来自存储过程的第一个查询,检索了album表中的所有行。下一个结果集来自第二个查询,从song表中检索行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
}
最后修改 October 10, 2024: 更新 (a4b8f85)