【问题标题】:Is it possible to retrieve a column value by name using GoLang database/sql是否可以使用 GoLang 数据库/sql 按名称检索列值
【发布时间】:2014-02-24 11:44:34
【问题描述】:

我见过的所有使用 sql.Row 的示例,通过 位置 访问查询的返回值:sql.Rows.scan() 需要正确定位中的正确类型变量scan() 参数对应于相应的列,以检索返回的每个列值,例如在以下示例中:

Example Based on GoDocs (with small mod):

rows, err := db.Query("SELECT name,age FROM users WHERE age>=50")
if err != nil {
    log.Fatal(err)
}
for rows.Next() {
    var name string
    var age int

    if err := rows.Scan(&name,&age); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s is %d\n", name, age)
}
if err := rows.Err(); err != nil {
    log.Fatal(err)
} 

&name&age 必须正确定位(第 0 列和第 1 列),Rows.Scan() 才能检索具有正确类型的正确值。

在我多年的生产系统开发中,我已经开始刻意避免这种做法,因为它不可靠:如果数据库更改列布局,如果它基于列位置,很容易破坏您的代码。

使用列名 来检索值要可靠得多 - 这可以使您免受数据库更改的影响,这些更改会添加或删除会破坏基于位置的代码的列。例如,在 Delphi 和 C# 中,所有的数据集,包括从查询返回值的列,都支持FieldByName('age').asIntegerfields['age'].value, 等。

在 Go 中有什么方法可以做到这一点?如果不是,这将是 Go 数据库支持的一个大缺点,并且令人严重失望 - 如上所述,这根本不安全。

编辑:

还有(也许这是一个新问题):我看到的例子似乎要求你检索查询返回的所有列,否则列的位置会倾斜。

假设在一个锁定的数据库中有一个我无法修改或添加的实用程序查询,它检索了几列,但我现在的任务只需要其中一个。基于当前的sql.Rows.Scan() 模型,我必须从我的应用程序代码中的查询中检索所有值,即使我不需要它们,而如果我可以查询"columnByName",那就没有必要了——我可以将我需要的数据带入我的应用程序代码。有什么解决方法吗?

【问题讨论】:

  • 有了column names,一个通过列名访问的实用函数将不难实现。
  • @dystroy - AFAIK,不是真的:我正在考虑使用列名作为键和列值作为值的映射。但是如果数据库模式发生变化,底层的 scan() 调用仍将依赖于位置和中断。也许你在想别的?
  • 按名称获取字段并不能真正保护您免受所有数据库更改的影响。对您的代码进行一些集成测试会更加健壮。
  • @alpe1 - 不知道你的意思是什么......
  • @ComeAndGo 我有一个我喜欢在运行 mysql 查询时实现的函数,它基本上将数据打包成字符串到字符串的映射切片 ([]map[string]string) .每个切片相当于一行,映射键字符串是列名,映射值字符串是该行的列值。这里有一个简单的 Playground 来演示它是如何工作的并分享 packageData 函数代码:play.golang.org/p/ctZYCn9EI9

标签: sql go


【解决方案1】:

是的,无需手动匹配列位置即可执行此操作。您可以使用一些第三方库来执行此操作,例如 sqlxgorp。我建议坚持使用其中之一,而不是自己动手。

命名匹配确实有轻微的惩罚。命名匹配与自己匹配列位置没有什么不同。它只是在运行时为您完成这项工作 - 可能在每次查询执行时。在任何其他语言中都是如此。

为什么在运行时?查询以字符串形式编写。必须对其进行解析才能确定位置。

如果您要制作自己的库,您如何自己做呢?

好的,让我们看看它是如何工作的。

type Person struct {
    Id int
    Name string
}
rows, err := db.Query("SELECT id, name FROM person;")
if err != nil {
    // handle err
    log.Fatal(err)
}
columnNames, err := rows.Columns() // []string{"id", "name"}
if err != nil {
    // handle err
    log.Fatal(err)
}
people = make([]Person, 0, 2)
for rows.Next() {
    person := Person{}
    // person == Person{0, ""}
    pointers := make([]interface{}, len(columnNames))
    // pointers == `[]interface{}{nil, nil}`
    structVal := reflect.ValueOf(person)
    for i, colName := range columnNames {
        fieldVal := structVal.FieldByName(strings.Title(colName))
        if !fieldVal.IsValid() {
            log.Fatal("field not valid")
        }
        pointers[i] = fieldVal.Addr().Interface()
    }
    // pointers == `[]interface{}{&int, &string}`
    err := rows.Scan(pointers...)
    if err != nil {
        // handle err
        log.Fatal(err)
    }
    // person == Person{1, "John Doe"}
    people = append(people, person)
}

【讨论】:

  • 本机不支持这个怎么办?
  • 列名是动态的。 Go 是静态编译的。所以你必须在运行时匹配名称。有图书馆可以为你做这件事。这个答案显示了它是如何完成的。
  • 列名永远不是动态的,无论如何您都必须重新编译代码才能更改您的 sql stmt/列名。我只是对 Go atm 中有多少 PITA 感到惊讶。即使使用 sqlx,也很棒。
  • 列名存储在字符串 var 中,无需重新编译即可在外部填充。这使它们不是静态的。结构字段名称是静态的。更改这些需要重新编译。
  • 即使在这种极端情况下,通过列名而不是列位置访问结果也是唯一有意义的事情
【解决方案2】:

做到这一点的唯一理智和干净的方法是使用:https://github.com/jmoiron/sqlx

假设你有一个 Place 结构:

type Place struct {
    Country       string
    City          sql.NullString
    TelephoneCode int `db:"telcode"`
}

您可以轻松扫描:

rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
    var p Place
    err = rows.StructScan(&p)
}

更多信息:http://jmoiron.github.io/sqlx/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-22
    • 1970-01-01
    • 1970-01-01
    • 2012-09-23
    • 2015-12-10
    • 2018-05-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多