是否可以使用GoLang database/sql按名称检索列值

yebdmbv4  于 2023-06-27  发布在  Go
关注(0)|答案(3)|浏览(123)

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

基于GoDocs的示例(带小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列),才能使用正确的类型检索正确的值。
在我多年的生产系统开发过程中,我一直在刻意避免这种做法,因为它不健壮:如果数据库对列布局的更改是基于列位置的,那么它很容易破坏代码。
使用列名来检索值要健壮得多--这将使您避免对数据库进行更改,这些更改会添加或删除列,从而破坏基于位置的代码。例如,在 Delphi 和C#中,所有数据集,包括从查询返回值的列,都支持FieldByName('age').asIntegerfields['age'].value,等。
在Go中有什么方法可以做到这一点吗?如果没有,这是Go数据库支持的一个很大的缺点,也是一个严重的失望--一点也不安全。

编辑:

  • (这可能是一个新问题)*:我所看到的示例似乎要求您检索查询返回的所有列,否则列的位置将发生偏移。

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

ep6jt1vc

ep6jt1vc1#

是的,可以这样做,而不必手动匹配列位置。您可以使用一些第三方库来完成此操作,例如sqlxgorp。我会建议坚持其中之一,而不是滚动自己的。
命名匹配确实有轻微的惩罚。命名匹配与自己匹配列位置没有什么不同。它只是在运行时为您完成这项工作-可能在每次查询执行时。这在任何其他语言中都是正确的。
为什么是runtime?查询被写成字符串。它必须被解析以确定位置。
如果你想建立自己的图书馆,你怎么做?

  • 行。列以获取列名和位置。
  • 将指针[]interface{}的一个片段传递给Rows。扫描以获取值。
  • reflect.Value和Value.Addr以获取指向目标值的指针。
  • 如果要Map到结构字段,则使用Value.FieldByName来获取结构字段的Value

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

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)
}
ktca8awb

ktca8awb2#

唯一明智和干净的方法是用途: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/

qij5mzcb

qij5mzcb3#

也许对数据采取不同的方法会有所帮助,就像这样:

func GetFieldValues(res *sql.Rows) []string {
    m, err := res.ColumnTypes()
    if err != nil {
        return nil
    }
    var (
        //i    int = 0
        mLen int = len(m)
    )
    if res.Next() {
        mValStr := make([]sql.NullString, mLen)
        mVal := make([]any, mLen)
        for i := range mVal {
            mVal[i] = &mValStr[i]
        }

        res.Scan(mVal...) //super

        mmVal := make([]string, mLen)
        for i := 0; i < mLen; i++ {
            if mValStr[i].Valid {
                mmVal[i] = mValStr[i].String
            } else {
                mmVal[i] = "" // empty string is nil in this context --> see func IsNull
            }
        }
        mVal = nil // Free allocated mem
        return mmVal

    } else {
        return nil
    }
}

相关问题