如何利用 Go 标准库的 database/sql 包来读取多条数据

Go

在查询 SQL 记录时,我发现您通常属于以下三种使用情况之一:

  1. 您要检索一条记录。例如,您可能想要查找特定用户。 (请参阅本文)
  2. 您想从同一表(或通过联接)检索许多记录。例如,您可能需要由特定用户创建的所有评论。
  3. 您对多个结果集感兴趣。这种用例很少见,但是当您要在查询之间使用一些中间数据时,通常会弹出该用例。例如,您想创建一个具有特定属性的临时用户表,然后查询有关该用户的许多信息。

直到最近,Go 的 database / sql 程序包仅支持前两个用例。要实现最后一个,您将需要在查询之间来回传递数据(或构造不同的 SQL 查询)。在 Go 1.8 中,添加了对多个结果集的支持,这使此过程变得更加简单。

在本文中,我们将介绍第二个用例-查询多个记录。

本文所需的数据

本文假设您在 Postgres 数据库中只有一个名为 users 的表,以及一些要查询的记录。它还假设您已在代码中与数据库建立了有效的连接(如果您需要与此相关的任何帮助,请参阅以前的帖子)。

您可以使用以下 SQL 代码创建一个表,并插入一些记录(如果还没有的话)。

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  age INT,
  first_name TEXT,
  last_name TEXT,
  email TEXT UNIQUE NOT NULL
);
INSERT INTO users (age, email, first_name, last_name)
VALUES (30, 'jon@calhoun.io', 'Jonathan', 'Calhoun');
INSERT INTO users (age, email, first_name, last_name)
VALUES (52, 'bob@smith.io', 'Bob', 'Smith');
INSERT INTO users (age, email, first_name, last_name)
VALUES (15, 'jerryjr123@gmail.com', 'Jerry', 'Seinfeld');

查询多条记录

在上一篇文章中,我们讨论了如何使用 QueryRow() 方法查询单条记录。我们将在此处介绍的大多数内容非常相似,但不是使用 QueryRow() 方法,而是使用 Query() 方法。

让我们直接跳到一个示例,然后解释一下是怎么回事。

rows, err := db.Query("SELECT id, first_name FROM users LIMIT $1", 3)
  if err != nil {
    // handle this error better than this
    panic(err)
  }
  defer rows.Close()
  for rows.Next() {
    var id int
    var firstName string
    err = rows.Scan(&id, &firstName)
    if err != nil {
      // handle this error
      panic(err)
    }
    fmt.Println(id, firstName)
  }
  // get any error encountered during iteration
  err = rows.Err()
  if err != nil {
    panic(err)
  }

就像 QueryRow() 一样,这允许我们传递参数并避免任何形式的 SQL 注入。这是我们在 SQL 语句的 $ 1 部分的第一行中执行的操作,然后将 3 参数传递给 Query() 方法。

QueryRow()Query() 之间的主要区别之一是如何处理错误。使用 QueryRow(),将错误处理推迟到您调用 Scan() 时,发现 0 条记录时将返回错误。Query() 的行为与此非常不同。

对于初学者来说,当找不到记录时,它不会返回错误。您可以通过将 LIMIT 更改为 0 来使用上述代码进行实际测试。所有发生的事情是我们的 for 循环从未真正运行过。

接下来要注意的是,调用 Query() 会同时返回指向 Rows 的指针以及错误。这意味着您可能甚至在开始迭代结果之前就可能遇到错误,因此您需要先检查一下错误。

假设没有错误,我们的* Rows对象不应为nil,我们可以继续执行并推迟调用以关闭行。我们通过调用defer rows.Close()来做到这一点。

在大多数情况下,您实际上不需要手动关闭行对象,因为在调用Next()方法并且不再有结果集时会发生这种情况,但是在发生错误的情况下,可能需要手动调用Close()。这也是一个幂等方法,意味着您可以多次调用它而不会产生任何负面影响,因此我建议通过defer进行调用,只要Rows不为零即可。

之后,我们进入for循环,在其中循环访问SQL语句返回的每个记录。我们通过调用rows.Next()来完成此操作,当成功准备下一行时返回true,否则返回false。

通常来说,rows.Next()false返回值表示没有更多记录了,但这并非总是如此。我们将在本系列的下一篇文章中看到多个结果集如何影响此结果,并且在准备下一行时发生错误时也有可能获得false返回值。这就是为什么您在示例末尾看到对rows.Err()的调用-这是我们在调用rows.Next()时验证是否没有错误的方法。

我怀疑代码是通过这种方式设计的,从而简化了编写循环的过程,但是这样做的缺点是忘记检查错误非常容易,因此*不要忘记调用rows.Err(),并检查错误!。

rows.Next()循环中,您可以使用rows变量,与使用单个Row的方式非常相似。您在每个单独的行上调用Scan()并传递数据的目标位置。如果存在一个,则该方法调用将返回错误,否则返回nil。一个例外是在这种情况下您永远不会收到ErrNoRows错误,但是您可能会收到抱怨在调用Next()之前调用Scan()

除了Scan()方法之外,还具有其他一些辅助方法,例如:

  • rows.Columns()-此方法用于检索SQL查询返回的每个列的名称。如果您正在编写类似sqlx的库,并且想要将struct标记映射到数据库中的列,则这很有用。
  • rows.ColumnTypes()-此方法用于检索有关每一列的信息,例如类型,长度或是否为空。我本人并没有经常使用它,所以我不确定Postgres对它的支持程度如何。

最终,我们可以打印出我们的数据(或使用它做你需要做的其他任何事情),并随程序一起运行。只要确保检查rows.Err()是否有任何错误!

常见错误

此时你可能会遇到的最常见错误是:

sql: expected 2 destination arguments in Scan, not 1

这意味着你将错误的参数数量传递给Scan() 方法。你需要为SQL语句检索的所有列传递足够的参数。如果不确定这是多少,建议你调用rows.Columns 方法,该方法将返回由SQL语句返回的列名称。

概要

就像其他讨论使用 Go 的database/sql包的文章一样,本文实际上并没有包含太多的 Go 代码。而 database/sql包旨在帮助你编写安全的 SQL 查询,避免一些陷阱。这样做的好处是,如果您已经了解 SQL,则无需学习太多知识就可以快速提高工作效率。

这种方法的缺点是,有时您可能会发现自己编写了更多代码,例如将指针传递User给该Scan()方法的对象中的每个字段 。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://www.calhoun.io/querying-for-mult...

译文地址:https://learnku.com/go/t/52433

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!