web-dev-qa-db-fra.com

lire les colonnes "SELECT *" dans la chaîne [] dans go

Je veux écrire un programme Go pour vider les lignes d'une table de base de données dans un fichier csv en utilisant SELECT *.

Go fournit l'excellent sql et csv apis, mais csv attend des tableaux de chaînes et la méthode Scan dans Rows "remplit" les champs selon leur type. Comme je ne connais pas le tableau auparavant, je n'ai aucune idée du nombre de colonnes et de leurs types.

C'est mon premier programme à Go, donc je me bats un peu.

Comment puis-je lire au mieux les colonnes d'une instance Rows dans un []string - et est-ce la "bonne" façon?

Merci!

[~ # ~] mise à jour [~ # ~]

Je lutte toujours avec les paramètres. Ceci est mon code, pour l'instant j'utilise panic au lieu de retourner un error, mais je vais changer cela plus tard. Dans mon test, je passe le résultat de la requête et os.Stdout.

func dumpTable(rows *sql.Rows, out io.Writer) error {
    colNames, err := rows.Columns()
    if err != nil {
        panic(err)
    }
    if rows.Next() {
        writer := csv.NewWriter(out)
        writer.Comma = '\t'
        cols := make([]string, len(colNames))
        processRow := func() {
            err := rows.Scan(cols...)
            if err != nil {
                panic(err)
            }
            writer.Write(cols)
        }
        processRow()
        for rows.Next() {
            processRow()
        }
        writer.Flush()
    }
    return nil
}

Pour cela, j'obtiens cannot use cols (type []string) as type []interface {} in function argument (à la ligne writer.Write(cols).

J'ai ensuite testé

    readCols := make([]interface{}, len(colNames))
    writeCols := make([]string, len(colNames))
    processRow := func() {
        err := rows.Scan(readCols...)
        if err != nil {
            panic(err)
        }
        // ... CONVERSION?
        writer.Write(writeCols)
    }

ce qui conduit à panic: sql: Scan error on column index 0: destination not a pointer.

MISE À JOUR 2

Je suis arrivé indépendamment à ANisus 'solution. C'est le code que j'utilise maintenant.

func dumpTable(rows *sql.Rows, out io.Writer) error {
    colNames, err := rows.Columns()
    if err != nil {
        panic(err)
    }
    writer := csv.NewWriter(out)
    writer.Comma = '\t'
    readCols := make([]interface{}, len(colNames))
    writeCols := make([]string, len(colNames))
    for i, _ := range writeCols {
        readCols[i] = &writeCols[i]
    }
    for rows.Next() {
        err := rows.Scan(readCols...)
        if err != nil {
            panic(err)
        }
        writer.Write(writeCols)
    }
    if err = rows.Err(); err != nil {
        panic(err)
    }
    writer.Flush()
    return nil
}
34
Arne

Pour directement Scan les valeurs dans un []string, vous devez créer un []interface{} tranche pointant vers chaque chaîne de votre tranche de chaîne.

Voici un exemple de travail pour MySQL (changez simplement le sql.Open- commande correspondant à vos paramètres):

package main

import (
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "database/sql"
)

func main() {
    db, err := sql.Open("mysql", "user:pass@tcp(localhost:3306)/test?charset=utf8")
    defer db.Close()

    if err != nil {
        fmt.Println("Failed to connect", err)
        return
    }

    rows, err := db.Query(`SELECT 'one' col1, 'two' col2, 3 col3, NULL col4`)
    if err != nil {
        fmt.Println("Failed to run query", err)
        return
    }

    cols, err := rows.Columns()
    if err != nil {
        fmt.Println("Failed to get columns", err)
        return
    }

    // Result is your slice string.
    rawResult := make([][]byte, len(cols))
    result := make([]string, len(cols))

    dest := make([]interface{}, len(cols)) // A temporary interface{} slice
    for i, _ := range rawResult {
        dest[i] = &rawResult[i] // Put pointers to each string in the interface slice
    }

    for rows.Next() {
        err = rows.Scan(dest...)
        if err != nil {
            fmt.Println("Failed to scan row", err)
            return
        }

        for i, raw := range rawResult {
            if raw == nil {
                result[i] = "\\N"
            } else {
                result[i] = string(raw)
            }
        }

        fmt.Printf("%#v\n", result)
    }
}
39
ANisus

pour obtenir le nombre de colonnes (ainsi que les noms), utilisez simplement la fonction Columns ()

http://golang.org/pkg/database/sql/#Rows.Columns

et comme csv ne peut être qu'une chaîne, utilisez simplement un type d'octet [] comme type dest pour le scanner. selon docu:

Si un argument a le type * [] octet, Scan enregistre dans cet argument une copie des données correspondantes. La copie appartient à l'appelant et peut être modifiée et conservée indéfiniment.

les données ne seront pas transformées en leur type réel. et à partir de cet [] octet, vous pouvez ensuite le convertir en chaîne.

si vous êtes sûr que vos tables utilisent uniquement des types de base (chaîne, [] octet, nil, int (s), flottant (s), bool), vous pouvez directement passer la chaîne en tant que dest

mais si vous utilisez d'autres types comme des tableaux, des énumérations, etc., les données ne peuvent pas être transformées en chaîne. mais cela dépend également de la façon dont le pilote gère ces types. (il y a quelques mois à titre d'exemple, le pilote postgres n'était pas capable de gérer les tableaux, donc il retournait toujours [] octet là où j'avais besoin de le transformer par moi-même)

5
fmt.Println.MKO

Ne pourrait-on pas faire cela à la place? Simplifié ci-dessous.

var tmpErrors string

_ = row.Scan(&tmpErrors)

actualVarHere := strings.Split(tmpErrors, "\n")

Y aurait-il un problème ou un problème de performances que je ne vois pas?

0
farhany

Le code suivant satisfait joliment votre condition, vous pouvez obtenir ce code à https://Gist.github.com/hygull/645c3dc39c69b6b69c06f5ea9deee41f . Les données du tableau ont également été fournies.

/**
    {
        "created_on": "26 may 2017",
        "todos": [
            "go get github.com/go-sql-driver/mysql"     
        ],
        "aim": "Reading fname column into []string(slice of strings)"
    }
*/


/* 
mysql> select * from users;
+----+-----------+----------+----------+-------------------------------+--------------+
| id | fname     | lname    | uname    | email                         | contact      |
+----+-----------+----------+----------+-------------------------------+--------------+
|  1 | Rishikesh | Agrawani | hygull   | [email protected] | 917353787704 |
|  2 | Sandeep   | E        | sandeep  | [email protected]       | 919739040038 |
|  3 | Darshan   | Sidar    | darshan  | [email protected]        | 917996917565 |
|  4 | Surendra  | Prajapat | surendra | [email protected]     | 918385894407 |
|  5 | Mukesh    | Jakhar   | mukesh   | [email protected]     | 919772254140 |
+----+-----------+----------+----------+-------------------------------+--------------+
5 rows in set (0.00 sec)

mysql> 
*/

package main
import "fmt"
import "log"
import (
    _"github.com/go-sql-driver/mysql"   
    "database/sql"
)

func main() {
    // db, err := sql.Open("mysql", "<username>:<password>@tcp(127.0.0.1:<port>)/<dbname>?charset=utf8" )
    db, err := sql.Open("mysql", "hygull:admin@67@tcp(127.0.0.1:3306)/practice_db?charset=utf8")

    if err != nil {
        log.Fatal(err)
    }

    rows, err := db.Query("select fname from users")

    if err != nil {
        log.Fatal(err)
    }

    firstnames:=[]string{}
    for rows.Next() {
        var fname string
        rows.Scan(&fname)
        firstnames = append(firstnames, fname)
    }

    fmt.Println(firstnames)
    db.Close()
}

/* 
[Rishikesh Sandeep Darshan Surendra Mukesh]
*/
0
hygull