如何利用 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');

查询单条记录

使用 SQL 时,创建记录后您可能要做的第一件事就是查询它。很想看到您刚刚保存在数据库中的内容并验证它是否确实存在,这是很自然的。这种查询通常会落入只关心单个记录的用例中,因此我们将从此处开始。

为此,我们将使用 QueryRow() 方法。这是 DB 类型提供的一种方法,用于执行预期返回单行的 SQL 查询。

请注意,我说过它期望一个单行-这意味着它不会期望返回 0 行,并且您会收到一个 ErrNoRows 发生这种情况时发生错误(当您调用 Scan() 时)。这是一个重要的细节,因为 Query() 之类的其他方法在不返回任何行时不会返回错误。有趣的是,如果您的 SQL 返回多行,这不会导致错误,而是仅使用第一个结果并丢弃其余结果。

当我们调用 QueryRow() 时,它将返回单个 *Row,从我们的角度来看这是一个相当简单的对象。它只有一种导出方法 Scan(),它将尝试将查询返回的数据复制到提供的目标位置(您传递给方法的参数是目标)。如果成功,它将返回 nil,否则将返回错误。如果未返回任何记录,您将在这里看到 ErrNoRows 错误-无法复制不存在的数据,因此在发生这种情况时会返回错误。

让我们来看看实际情况。

sqlStatement := `SELECT id, email FROM users WHERE id=$1;`
var email string
var id int
// Replace 3 with an ID from your database or another random
// value to test the no rows use case.
row := db.QueryRow(sqlStatement, 3)
switch err := row.Scan(&id, &email); err {
case sql.ErrNoRows:
  fmt.Println("No rows were returned!")
case nil:
  fmt.Println(id, email)
default:
  panic(err)
}

剖析示例代码

现在,让我们花点时间来剖析上一节中示例代码中的操作。在第一行中,我们构造 SQL 语句。

sqlStatement := `SELECT id, email FROM users WHERE id=$1;`

我们在本系列的其他文章中已经对此进行了介绍,因此我们不会对此进行过多探讨,但是简短的版本是这将主要模拟原始 SQL,并使用变量 $ 1 来替换任何原始 SQL。我们要插入查询中的变量。在这种情况下,我们使用它来插入我们要查询的记录的 ID。

之后,我们声明一些变量。这是非常标准的Go代码,你应该很熟悉。

接下来,我们使用db变量(类型为 sql.DB)使的QueryRow()方法 。像前面一样,我们将SQL语句作为第一个参数传递,而我们要提供的用于构造SQL语句的任何数据都作为附加参数传递。在执行查询后,QueryRow()将返回指向数据的指针sql.Row

错误返回会定义在你调用Row对象的Scan()方法时,并且 QueryRow() 永远不要返回nil(至少与Go 1.8中当前编写的一样),因此你可能不需要检查错误是否未nil。

之后,我们拥有以下代码行:

switch err := row.Scan(&id, &email); err { ... }

这行代码正在做两件事。首先,它调用row.Scan()方法,将指向我们idemail的指针作为参数传递进去,这是为了告诉Scan()方法查询的数据复制到这些变量使用的内存位置,Scan()方法如果成功则将返回nil,否则将返回错误。

该行代码的后半部分(即 err { 部分)告诉我们的程序err在switch语句中使用的值 。这使我们能够为可能遇到的每种错误添加处理方法。

我们检查的第一种情况是没有返回任何数据时的情况。

case sql.ErrNoRows:
  fmt.Println("No rows were returned!")

我故意指出这一点,因为这是正常recover的最简单的错误情况。在这种情况下,我们只是简单地打印出没有返回行,但是在你自己的应用程序中,你可能会执行类似将用户重定向到404页面的操作。

在此示例中,我们仅查询两个属性(idemail),但是如果需要,你可以使用它 * 来查询所有这些属性。

我们讨论的第二种情况是根本没有错误。

case nil:
  fmt.Println(id, email)

在示例代码中,我们仅打印出查询到的值,但是在你的代码中,这可能是你的函数继续执行的情况,或者它可能返回查询到的数据。

我们讨论的最后一种情况是默认情况。仅当存在错误(即不是nil)且错误不是ErrNowRows时才会发生。

default:
  panic(err)

在此示例代码段中,我们使用了panic,但在您的应用程序中,您可能希望正常处理错误。这可能只是意味着将发送给用户500页面,但这通常表示数据库有问题,或者SQL查询不对。

我们也可以查询更多数据

在原始示例中,我们仅查询了几列,但是通常可能希望查询全部记录。下面显示使用User类型与我们匹配的users表进行查询。

type User struct {
  ID        int
  Age       int
  FirstName string
  LastName  string
  Email     string
}

sqlStatement := `SELECT * FROM users WHERE id=$1;`
var user User
row := db.QueryRow(sqlStatement, 3)
err := row.Scan(&user.ID, &user.Age, &user.FirstName,
  &user.LastName, &user.Email)
switch err {
case sql.ErrNoRows:
  fmt.Println("No rows were returned!")
  return
case nil:
  fmt.Println(user)
default:
  panic(err)
}

当我们调用Scan()方法时我们将传递User对象的指针字段,以便可以使用从数据库中检索到的用户记录来填充它们。虽然一开始似乎要编写很多代码,但额外的好处是我们可以显式决定将哪些数据映射到User类型中的每个字段 。

在本系列的后续文章中,我们将讨论一些第三方库(如sqlx 和 SQLBoiler)如何使用结构体tag来简化此过程,而其他第三方库(如 GORM)如何使用结构体本身来定义数据库。

概要

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

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

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

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

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

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

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