2015-02-21 18 views
9

Sto scrivendo un programma di base per leggere i valori dalla tabella del database e stampare nella tabella. Il tavolo era popolato da un programma antico. Alcuni dei campi nella riga sono opzionali e quando provo a leggerli come stringa, ottengo il seguente errore:Come posso gestire i valori di ritorno nil dal database?

panic: sql: Scan error on column index 2: unsupported driver -> Scan pair: <nil> -> *string 

Dopo aver letto le altre domande per problemi simili, mi si avvicinò con seguente codice per gestire il valori nulli. Il metodo funziona bene nella pratica. Ottengo i valori in testo normale e stringa vuota invece dei valori nulli.

Tuttavia, ho due preoccupazioni:

  1. Questo non sembra efficiente. Ho bisogno di gestire più di 25 campi come questo e questo significherebbe che li leggo come byte e li converto in stringa. Troppe chiamate e conversioni di funzioni. Due strutture per gestire i dati e così via ...
  2. Il codice sembra brutto. Sembra già contorto con 2 campi e diventa illeggibile quando vado a 25+

Sto sbagliando? C'è un modo migliore/più pulito/efficace/idiomatico per leggere i valori dal database?

Trovo difficile credere che un linguaggio moderno come Go non gestisca il database con un ritorno elegante.

Grazie in anticipo!

Codice frammento:

// DB read format 
type udInfoBytes struct { 
    id      []byte 
    state     []byte 
} 

// output format 
type udInfo struct { 
    id      string 
    state     string 
} 

func CToGoString(c []byte) string { 
    n := -1 
    for i, b := range c { 
    if b == 0 { 
     break 
    } 
    n = i 
    } 
    return string(c[:n+1]) 
} 

func dbBytesToString(in udInfoBytes) udInfo { 

    var out udInfo 
    var s string 
    var t int 

    out.id = CToGoString(in.id) 
    out.state = stateName(in.state) 
    return out 
} 

func GetInfo(ud string) udInfo { 

    db := getFileHandle() 
    q := fmt.Sprintf("SELECT id,state FROM Mytable WHERE id='%s' ", ud) 

    rows, err := db.Query(q) 
    if err != nil { 
    log.Fatal(err) 
    } 
    defer rows.Close() 
    ret := udInfo{} 
    r := udInfoBytes{} 
    for rows.Next() { 
    err := rows.Scan(&r.id, &r.state) 

    if err != nil { 
     log.Println(err) 
    } 
    break 
    } 
    err = rows.Err() 
    if err != nil { 
    log.Fatal(err) 
    } 

    ret = dbBytesToString(r) 
    defer db.Close() 
    return ret 
} 

edit:

voglio avere qualcosa come il seguente dove faccio non devono preoccuparsi di gestire NULL e leggerli stringa come vuota automaticamente.

// output format 
type udInfo struct { 
    id      string 
    state     string 
} 

func GetInfo(ud string) udInfo { 

    db := getFileHandle() 
    q := fmt.Sprintf("SELECT id,state FROM Mytable WHERE id='%s' ", ud) 

    rows, err := db.Query(q) 
    if err != nil { 
    log.Fatal(err) 
    } 
    defer rows.Close() 
    r := udInfo{} 

    for rows.Next() { 
    err := rows.Scan(&r.id, &r.state) 

    if err != nil { 
     log.Println(err) 
    } 
    break 
    } 
    err = rows.Err() 
    if err != nil { 
    log.Fatal(err) 
    } 

    defer db.Close() 
    return r 
} 
+0

Grazie per le risposte. Vedo che molte soluzioni suggeriscono l'uso di sql.NullString o della libreria specifica del database. Desideravo che ci fosse qualcosa di generico che sarebbe stato eseguito su "database/sql". Per il momento userò la soluzione sql.NullString perché devo collegarmi a un database sqlserver remoto via ODBC (lo so, doloroso). – FlowRaja

risposta

19

Ci sono tipi separato per gestire null valori provenienti dal database come sql.NullBool, sql.NullFloat64, ecc

Ad esempio: banca dati/sql puntatore maniglia pacchetto

var s sql.NullString 
err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s) 
... 
if s.Valid { 
    // use s.String 
} else { 
    // NULL value 
} 
+0

Ciò non impedisce ancora il mio problema di copiare da un tipo all'altro. Il modo in cui lo vedo, per ciascuno dei 25+ valori, devo copiare dal tipo NullString al tipo String. Anche se, mi aiuta a sbarazzarsi delle funzioni, che è il benvenuto. Quindi grazie. – FlowRaja

+1

Sì, devi gestire i valori Null. Nel tuo caso particolare, potresti scrivere un involucro per questo. Inoltre, potresti trovare utile il pacchetto https://github.com/jmoiron/sqlx, che può semplificare il tuo compito. – divan

8

di andare il genere.

package main 

import (
    "database/sql" 
    "fmt" 
    _ "github.com/mattn/go-sqlite3" 
    "log" 
) 

func main() { 
    db, err := sql.Open("sqlite3", ":memory:") 
    if err != nil { 
     log.Fatal(err) 
    } 
    defer db.Close() 

    _, err = db.Exec("create table foo(id integer primary key, value text)") 
    if err != nil { 
     log.Fatal(err) 
    } 
    _, err = db.Exec("insert into foo(value) values(null)") 
    if err != nil { 
     log.Fatal(err) 
    } 
    _, err = db.Exec("insert into foo(value) values('bar')") 
    if err != nil { 
     log.Fatal(err) 
    } 
    rows, err := db.Query("select id, value from foo") 
    if err != nil { 
     log.Fatal(err) 
    } 
    for rows.Next() { 
     var id int 
     var value *string 
     err = rows.Scan(&id, &value) 
     if err != nil { 
      log.Fatal(err) 
     } 
     if value != nil { 
      fmt.Println(id, *value) 
     } else { 
      fmt.Println(id, value) 
     } 
    } 
} 

Si dovrebbe ottenere come di seguito:

1 <nil> 
2 bar 
-2

Ho iniziato a utilizzare il driver MyMySql in quanto utilizza un'interfaccia più gradevole a quello della biblioteca std.

https://github.com/ziutek/mymysql

Ho poi avvolto l'interrogazione del database in semplice da usare funzioni.Questa è una tale funzione:

import "github.com/ziutek/mymysql/mysql" 
import _ "github.com/ziutek/mymysql/native" 

// Execute a prepared statement expecting multiple results. 
func Query(sql string, params ...interface{}) (rows []mysql.Row, err error) { 
    statement, err := db.Prepare(sql) 
    if err != nil { 
     return 
    } 
    result, err := statement.Run(params...) 
    if err != nil { 
     return 
    } 
    rows, err = result.GetRows() 
    return 
} 

Per utilizzare questo è semplice come questo frammento:

rows, err := Query("SELECT * FROM table WHERE column = ?", param) 

for _, row := range rows { 
    column1 = row.Str(0) 
    column2 = row.Int(1) 
    column3 = row.Bool(2) 
    column4 = row.Date(3) 
    // etc... 
} 

Avviso belle metodi di riga per costringere a un valore particolare. Null sono gestite dalla libreria e le regole sono documentate qui:

https://github.com/ziutek/mymysql/blob/master/mysql/row.go

1

Una soluzione alternativa potrebbe essere quella di gestire questa nell'istruzione SQL stesso utilizzando la funzione COALESCE (anche se non tutti DB può sostenere questo).

Per esempio si potrebbe invece utilizzare:

q := fmt.Sprintf("SELECT id,COALESCE(state, '') as state FROM Mytable WHERE id='%s' ", ud) 

che sarebbe effettivamente dare 'stato' un valore di default di una stringa vuota nel caso in cui è stato memorizzato come un valore NULL nel db.

Problemi correlati