Можно ли получить значение столбца по имени с помощью GoLang database/sql

все примеры, которые я видел за использование sql.Row, доступ к возвращаемым значениям из запросов по позиция:sql.Rows.scan() требуется правильно введенная переменная правильно расположенный на scan() аргументы, соответствующие соответствующему столбцу, чтобы получить каждое возвращаемое значение столбца, например, в следующем примере:

пример на основе GoDocs (с небольшим модом):

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 %dn", name, age)
}
if err := rows.Err(); err != nil {
    log.Fatal(err)
} 

&name и &age должно быть расположено правильно (столбцы 0 и 1) для строк.Scan (), чтобы получить правильные значения с правильными типами.

за годы разработки для производственных систем я старательно избегал этой практики, потому что она не является надежной: изменение базы данных в макете столбцов легко сломает ваш код, если он основан на позициях столбцов.

он гораздо более надежен в использовании имена столбцов для извлечения значений-это изолирует вы из изменений в базе данных, которые добавляют или удаляют столбцы, которые искажают код на основе позиции. Например, в Delphi и C#, все наборы данных, включая столбцы, возвращающие значения из запросов, поддержка FieldByName('age').asInteger или fields['age'].value, etc.

любой способ сделать это в Go? Если нет, это большой недостаток в поддержке базы данных Go и серьезное разочарование - совсем не безопасно, как упоминалось.

Edit:

также (возможно, это новый вопрос): примеры, которые я видел, похоже, требуют, чтобы вы извлекли все столбцы, возвращаемые запросом, или позиции столбцов будут искажены.

предположим, что в заблокированной базе данных есть запрос утилиты, который я не могу изменить или добавить, и он извлекает несколько столбцов, но мне нужен только один из них для моей текущей задачи. На основе текущего sql.Rows.Scan() модель, я должен получить все значения из запроса в моем коде приложения, даже если они мне не нужны, тогда как если бы я мог запросить "columnByName" это не было бы необходимо - я мог бы просто ввести в свой код приложения данные, которые мне нужны. Что-нибудь для этого придумали?

2 ответов


Да, это можно сделать без необходимости вручную сопоставлять позиции столбцов. Для этого можно использовать сторонние библиотеки, такие как sqlx или горп. Я бы рекомендовал придерживаться одного из них, а не сворачивать свой собственный.

именованное сопоставление имеет небольшой штраф. Именованное сопоставление ничем не отличается от сопоставления позиций столбцов самостоятельно. Он просто делает эту работу для вас во время выполнения - возможно, на каждом запросе исполнение. Это верно для любого другого языка.

почему во время выполнения? Запрос записывается в виде строки. Он должен быть проанализирован, чтобы определить положение.

Если вы должны были сделать свою собственную библиотеку, как вы делаете это самостоятельно?

Ok, так что давайте посмотрим, как это работает.

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

единственный разумный и чистый способ сделать это-использовать:https://github.com/jmoiron/sqlx

Допустим, у вас есть структура места:

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/