Як перетворити рядок бази даних у структуру


80

Скажімо, у мене є структура:

type User struct {
    Name  string
    Id    int
    Score int
}

І таблиця бази даних з тією ж схемою. Який найпростіший спосіб проаналізувати рядок бази даних на структуру? Я додав відповідь нижче, але не впевнений, що вона найкраща.

Відповіді:


99

Тести пакету Go часто дають підказки щодо способів робити щось. Наприклад, від database/sql/sql_test.go,

func TestQuery(t *testing.T) {
    /* . . . */
    rows, err := db.Query("SELECT|people|age,name|")
    if err != nil {
            t.Fatalf("Query: %v", err)
    }
    type row struct {
            age  int
            name string
    }
    got := []row{}
    for rows.Next() {
            var r row
            err = rows.Scan(&r.age, &r.name)
            if err != nil {
                    t.Fatalf("Scan: %v", err)
            }
            got = append(got, r)
    }
    /* . . . */
}

func TestQueryRow(t *testing.T) {
    /* . . . */
    var name string
    var age int
    var birthday time.Time
    err := db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&age)
    /* . . . */
}

Що для вашого запитання, запитуючи рядок у структуру, означало б щось на зразок:

var row struct {
    age  int
    name string
}
err = db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&row.age, &row.name)

Я знаю, що це схоже на ваше рішення, але важливо показати, як знайти рішення.


78
якщо найпростіший спосіб - це вручну прив’язати стовпці до полів struct, мені цікаво, який складний спосіб
Anthony Hunt

10
На жаль, це не дуже зручно, особливо у випадку більших структур - прив'язка вручну до властивостей struct - це повний збій ... Використання jmoiron / sqlx або деяких інших бібліотек є більш ефективним ...
shadyyx,

я нічого не повертаю з рядків. Сканувати (). всі вари встановлені порожніми.
filthy_wizard

14
Який біс тут синтаксис конвеєра? "ВИБРАТИ | людей"? Я не бачу жодного посилання на це в godocs.
Брайан

9
Був той самий момент WTF, що і Брайан. Виявляється, це фальшивий драйвер, який вони створили виключно для тестування sql / database ( golang.org/src/database/sql/fakedb_test.go ). Я справді сподівався, що зможу використовувати його для нового коду!
Trey Stout

58

Я рекомендую github.com/jmoiron/sqlx .

З README:

sqlx - це бібліотека, яка надає набір розширень стандартної database/sqlбібліотеки go . У SQLX версії sql.DB, sql.TX, sql.Stmtта ін. всі залишають базові інтерфейси недоторканими, так що їх інтерфейси є надмножиною на стандартних. Це робить відносно безболісною інтеграцію існуючих баз коду за допомогою бази даних / sql з sqlx.

Основними додатковими поняттями є:

  • Маршал рядує на структури (із вбудованою підтримкою структури), карти та фрагменти
  • Підтримка іменованих параметрів, включаючи підготовлені оператори
  • Getі Selectшвидко перейти від запиту до структури / фрагмента

README також включає фрагмент коду, що демонструє сканування рядка у структуру:

type Place struct {
    Country       string
    City          sql.NullString
    TelephoneCode int `db:"telcode"`
}
// Loop through rows using only one struct
place := Place{}
rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
    err := rows.StructScan(&place)
    if err != nil {
        log.Fatalln(err)
    } 
    fmt.Printf("%#v\n", place)
}

Зверніть увагу, що нам не потрібно було вручну зіставляти кожен стовпець із полем структури. sqlx має деякі зіставлення за замовчуванням для полів struct зі стовпцями бази даних, а також може визначати стовпці бази даних за допомогою тегів (зверніть увагу на TelephoneCodeполе Placeструктури вище). Детальніше про це ви можете прочитати в документації .


37

Ось один із способів зробити це - просто призначити всі значення структури вручну у Scanфункції.

func getUser(name string) (*User, error) {
    var u User
    // this calls sql.Open, etc.
    db := getConnection()
    // note the below syntax only works for postgres
    err := db.QueryRow("SELECT * FROM users WHERE name = $1", name).Scan(&u.Id, &u.Name, &u.Score)
    if err != nil {
        return &User{}, err
    } else {
        return &u, nil
    }
}

1
@eslammostafa, в який момент цей код може мати проблеми зі значеннями NULL?
deFreitas

Наприклад, @deFreitas у підсумку, я мав на увазі нульові значення, які надходять з бази даних.
she7ata

@eslammostafa Я розумію, це справді біль
deFreitas

@deFreitas тим часом я використовую sql.NullString і sql.NullInt64 ..etc для обробки нульових значень, хоча це трохи додаткова робота.
she7ata

@eslammostafa проблема виникає, якщо ви використовуєте sql.NullStringта перетворюєте структуру на, JSONто згенерована не є дружньою, потрібно використовувати VOабо щось подібне
deFreitas

7
rows, err := connection.Query("SELECT `id`, `username`, `email` FROM `users`")

if err != nil {
    panic(err.Error())
}

for rows.Next() {
    var user User

    if err := rows.Scan(&user.Id, &user.Username, &user.Email); err != nil {
        log.Println(err.Error())
    }

    users = append(users, user)
}

Повний приклад


1

є пакет саме для цього: sqlstruct

на жаль, минулого разу, коли я перевірив, що він не підтримує вбудовані структури (які тривіально реалізовувати самостійно - у мене був робочий прототип за кілька годин).

щойно здійснив зміни, які я вніс до sqlstruct


-1

використання: go-models-mysql sqlbuilder

val, err = m.ScanRowType(row, (*UserTb)(nil))

або повний код

import (
    "database/sql"
    "fmt"

    lib "github.com/eehsiao/go-models-lib"
    mysql "github.com/eehsiao/go-models-mysql"
)

// MyUserDao : extend from mysql.Dao
type MyUserDao struct {
    *mysql.Dao
}

// UserTb : sql table struct that to store into mysql
type UserTb struct {
    Name       sql.NullString `TbField:"Name"`
    Id         int            `TbField:"Id"`
    Score      int            `TbField:"Score"`
}

// GetFirstUser : this is a data logical function, you can write more logical in there
// sample data logical function to get the first user
func (m *MyUserDao) GetFirstUser() (user *User, err error) {

    m.Select("Name", "Id", "Score").From("user").Limit(1)
    fmt.Println("GetFirstUser", m.BuildSelectSQL().BuildedSQL())
    var (
        val interface{}
        row *sql.Row
    )

    if row, err = m.GetRow(); err == nil {
        if val, err = m.ScanRowType(row, (*UserTb)(nil)); err == nil {
            u, _ := val.(*UserTb)

            user = &User{
                Name:       lib.Iif(u.Name.Valid, u.Nae.String, "").(string),
                Id:         u.Id,
                Score:      u.Score,
            }
        }
    }
    row, val = nil, nil

    return
}

-1

Ось бібліотека саме для цього: scany .

Ви можете використовувати його так:

type User struct {
    Name  string
    Id    int
    Score int
}

// db is your *sql.DB instance
// ctx is your current context.Context instance

// Use sqlscan.Select to query multiple records.
var users []*User
sqlscan.Select(ctx, db, &users, `SELECT name, id, score FROM users`)

// Use sqlscan.Get to query exactly one record.
var user User
sqlscan.Get(ctx, db, &user, `SELECT name, id, score FROM users WHERE id=123`)

Це добре задокументовано і з ним легко працювати.

Застереження: Я є автором цієї бібліотеки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.