2013-01-23 1 views
21

Chcę napisać program Go, aby zrzuty wierszy z tabeli bazy danych do pliku csv przy użyciu SELECT *.odczytać kolumny "SELECT *" na ciąg znaków [] w go

Go zapewnia doskonałe sql i csv API, ale csv spodziewa tablic ciągów i metoda w RowsScan „wypełnia” pól w zależności od ich rodzaju. Ponieważ nie znam tabeli wcześniej, nie mam pojęcia, ile kolumn jest i jakie są ich typy.

To mój pierwszy program w Go, więc trochę się zmagam.

Jak najlepiej odczytać kolumny z instancji Rows w []string - i czy jest to "właściwy" sposób?

Dzięki!

UPDATE

ja wciąż zmaga się z parametrami. To jest mój kod, na razie używam panic zamiast zwracać error, ale zamierzam to później zmienić. W moim teście przekazuję wynik zapytania i 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 
} 

W tym uzyskać cannot use cols (type []string) as type []interface {} in function argument (w linii writer.Write(cols).

Następnie testowane

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

które prowadzą do panic: sql: Scan error on column index 0: destination not a pointer.

UPDATE 2

Niezależnie przybyłem do rozwiązania ANisus. To jest kod, którego teraz używam.

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 
} 

Odpowiedz

23

W celu bezpośredniego Scan Wartości w []string, ty musi utworzyć plasterek []interface{} wskazujący na każdy ciąg w wycinku łańcucha.

Tu masz przykład pracy dla MySQL (wystarczy zmienić sql.Open -polecenie dopasować ustawienia):

package main 

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

func main() { 
    db, err := sql.Open("mysql", "user:[email protected](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) 
    } 
} 
+0

Hej, świetnie! Właśnie tam przybyłem i chciałem opublikować go w edycji. Dzięki! To będzie moja odpowiedź, a ja ją przegłosuję, ale wciąż mam pytanie: chcę, żeby to było szybkie i chciałbym uciec od wartości ciągu i przekonwertować zero do \ N. Jak najlepiej uwzględnić konwersje w moim kodzie? – Arne

+0

Moje rozwiązanie jest dalekie od wodoszczelności. Wymaga, aby wartości mogły być przechowywane w 'ciągu'. Na przykład. jeśli otrzymasz wartość NULL z bazy danych, pojawi się błąd, ponieważ ciąg nie może mieć wartości 'nil'. Zaktualizowałem odpowiedź za pomocą bajtu [] i sprawdzając wartości 'nil'. – ANisus

+0

Możesz rozważyć użycie 'sql.RawBytes' zamiast' [] byte' –

5

aby liczba kolumn (a także nazwiska) wystarczy użyć kolumny() Funkcja

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

i jako csv może być tylko struny, wystarczy użyć [] bajt wpisz jako typ dest dla skanera. według doku:

If an argument has type *[]byte, Scan saves in that argument a copy of the corresponding data. The copy is owned by the caller and can be modified and held indefinitely.

dane nie zostaną przekształcone w jego rzeczywistej typu. iz tego [] bajtu możesz następnie przekonwertować go na ciąg.

jeśli jesteś pewien tabele używać tylko typy bazowe (string [] bajt Nil, int (e), pływak (y), bool) można bezpośrednio przekazać ciąg jako dest

ale jeśli używasz inne typy, takie jak tablice, wyliczenia itd., dane nie mogą zostać przekształcone na ciągi. ale zależy to również od tego, jak sterownik obsługuje te typy.(Kilka miesięcy temu jako przykład, kierowca postgres nie był w stanie obsłużyć tablice, więc wrócił zawsze [] bajt, gdzie potrzebne do przekształcenia go przez mój własny)

+0

Dzięki za to, że dotyczy MySQL, więc jest to tylko typ podstawowy. – Arne

+0

Upewnij się, że akceptujesz tę odpowiedź, jeśli działa ona dla ciebie (i jeśli nie ma lepszych odpowiedzi) – weberc2

+0

Będę, ale wciąż mam problem z konwersją z '[] interface {}' w Scan to '[] string' w Write. Przekazanie '[] string' do Scan nie zostało skompilowane - po prostu nie edytowałem jeszcze mojej odpowiedzi. Ale udało mi się przegłosować. – Arne

0

Poniższy kod ładnie statisfies swoje wymagania, można uzyskać ten kod na https://gist.github.com/hygull/645c3dc39c69b6b69c06f5ea9deee41f. Dane tabeli również zostały dostarczone.

/** 
    { 
     "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:[email protected]@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] 
*/