Wie konvertiere ich eine Datenbankzeile in eine Struktur?

78

Angenommen, ich habe eine Struktur:

type User struct {
    Name  string
    Id    int
    Score int
}

Und eine Datenbanktabelle mit demselben Schema. Was ist der einfachste Weg, eine Datenbankzeile in eine Struktur zu analysieren? Ich habe unten eine Antwort hinzugefügt, bin mir aber nicht sicher, ob es die beste ist.

Kevin Burke
quelle

Antworten:

95

Go-Pakettests liefern häufig Hinweise darauf, wie Dinge zu tun sind. Zum Beispiel von 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)
    /* . . . */
}

Was für Ihre Frage, wenn Sie eine Zeile in eine Struktur abfragen, zu etwas führen würde wie:

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

Ich weiß, dass dies Ihrer Lösung ähnlich sieht, aber es ist wichtig zu zeigen, wie Sie eine Lösung finden.

peterSO
quelle
75
Wenn der einfache Weg darin besteht, Spalten manuell an Strukturfelder zu binden, frage ich mich, was der schwierige Weg ist
Anthony Hunt
10
Leider ist dies nicht sehr praktisch, insbesondere bei größeren Strukturen - das manuelle Binden an Struktureigenschaften ist ein völliger Fehler ... Die Verwendung von jmoiron / sqlx oder einigen anderen Bibliotheken ist effizienter ...
Shadyyx
Ich bekomme immer wieder nichts von Zeilen zurück. Scan (). Alle Vars sind leer.
filthy_wizard
14
Was zum Teufel ist die Pipe-Syntax hier? "SELECT | people"? Ich sehe keinen Hinweis darauf in den Godocs.
Brian
9
Hatte den gleichen WTF-Moment wie Brian. Es stellte sich heraus, dass es sich um einen gefälschten Treiber handelt, den sie ausschließlich zum Testen von SQL / Datenbank erstellt haben ( golang.org/src/database/sql/fakedb_test.go ). Ich hatte wirklich gehofft, dass ich ihn für neuen Code verwenden kann!
Trey Stout
57

Ich empfehle github.com/jmoiron/sqlx .

Aus der README:

sqlx ist eine Bibliothek, die eine Reihe von Erweiterungen für die Standardbibliothek von go bereitstellt database/sql. Die SQLX Versionen sql.DB, sql.TX, sql.Stmtet al. Alle lassen die zugrunde liegenden Schnittstellen unberührt, so dass ihre Schnittstellen eine Obermenge der Standardschnittstellen sind. Dies macht es relativ einfach, vorhandene Codebasen mithilfe von database / sql in sqlx zu integrieren.

Wichtige zusätzliche Konzepte sind:

  • Marschall-Zeilen in Strukturen (mit eingebetteter Strukturunterstützung), Karten und Slices
  • Unterstützung für benannte Parameter einschließlich vorbereiteter Anweisungen
  • Getund Selectschnell von der Abfrage zur Struktur / Slice zu gelangen

Die README-Datei enthält auch ein Code-Snippet, das das Scannen einer Zeile in eine Struktur demonstriert:

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

Beachten Sie, dass wir nicht jede Spalte manuell einem Feld der Struktur zuordnen mussten. sqlx verfügt über einige Standardzuordnungen für Strukturfelder zu Datenbankspalten sowie über die Angabe von Datenbankspalten mithilfe von Tags (beachten Sie das TelephoneCodeFeld der Placeobigen Struktur). Mehr dazu lesen Sie in der Dokumentation .

ckeeney
quelle
38

Hier ist eine Möglichkeit, dies zu tun: Weisen Sie einfach alle Strukturwerte manuell in der ScanFunktion zu.

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
    }
}
Kevin Burke
quelle
1
@eslammostafa Ab wann kann dieser Code Probleme mit NULL-Werten haben?
DeFreitas
@deFreitas bei Punktzahl zum Beispiel meinte ich Nullwerte, die aus der Datenbank kommen.
She7ata
@eslammostafa Ich verstehe, das ist wirklich ein Schmerz
deFreitas
@deFreitas In der Zwischenzeit verwende ich sql.NullString und sql.NullInt64 ..etc, um Nullwerte zu verarbeiten. Es ist jedoch ein wenig zusätzliche Arbeit.
she7ata
@eslammostafa das Problem gibt es, wenn Sie sql.NullStringdie Struktur verwenden und konvertieren, JSONdann ist die generierte nicht freundlich, muss eine VOoder so etwas verwenden
deFreitas
8
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)
}

Vollständiges Beispiel

Роман Журавель
quelle
2

Dafür gibt es ein Paket: sqlstruct

Als ich das letzte Mal nachgesehen habe, hat es leider keine eingebetteten Strukturen unterstützt (die einfach zu implementieren sind - ich hatte in wenigen Stunden einen funktionierenden Prototyp).

Ich habe gerade die Änderungen übernommen, die ich an sqlstruct vorgenommen habe

deemok
quelle
0

Verwenden Sie: go-models- mysql sqlbuilder

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

oder den vollständigen Code

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
}
EE Hsiao
quelle
0

Hier ist eine Bibliothek nur dafür: scany .

Sie können es so verwenden:

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

Es ist gut dokumentiert und einfach zu bearbeiten.

Haftungsausschluss: Ich bin der Autor dieser Bibliothek.

Georgy Savva
quelle