2013-01-23 18 views
21

我想編寫一個Go程序,使用SELECT *將數據庫表中的行轉儲爲csv文件。將「SELECT *」列讀入[]中的字符串

轉到提供了優良sqlcsv的API,但是csv期望字符串數組,並根據它們的類型在RowsScan方法「填充」字段。 因爲我以前不知道表格,所以我不知道有多少列以及它們的類型是什麼。

這是我在Go的第一個程序,所以我掙扎了一下。

如何最好地將Rows實例中的列讀入[]string - 這是「正確」的方式嗎?

謝謝!

UPDATE

我仍與參數掙扎。這是我的代碼,現在我正在使用panic而不是返回error,但我稍後會改變它。在我的測試中,我傳遞了查詢結果和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 
} 

對於這一點,我得到cannot use cols (type []string) as type []interface {} in function argument(在writer.Write(cols)線。

然後我測試

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

這導致panic: sql: Scan error on column index 0: destination not a pointer

UPDATE 2

我獨立抵達ANisus'解決方案。這是我現在使用的代碼。

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 
} 

回答

23

爲了直接將Scan的值轉換爲[]string,必須創建一個[]interface{}切片指向您的字符串切片中的每個字符串。

在這裏,你有MySQL的工作示例(只是改變sql.Open -command來匹配您的設置):

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

嘿,太棒了!我剛到達那裏,想發佈一個編輯。謝謝!這將是我的答案,我會upvote它,但我仍然有一個問題:我希望這是快速的,我想逃避字符串值並將nil轉換爲\ N。我如何最好地在我的代碼中包含轉換? – Arne

+0

我的解決方案遠離水密。它要求值能夠存儲在一個「字符串」中。例如。如果你從數據庫中獲得一個NULL值,你將會有一個錯誤,因爲字符串不能是'nil'。我使用[]字節更新了答案並檢查了'nil'值。 – ANisus

+0

您可能會考慮使用'sql.RawBytes'而不是'[] byte' –

5

得到列數(也名)只使用列()函數

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

,併爲CSV只能是一個字符串,只需使用[]字節鍵入掃描儀的目標類型。根據實況 :

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.

的數據將不被轉化成它的實數型。 ,然後從這個[]字節中將其轉換爲字符串。

,如果你確信你的表只使用基本類型(字符串,[]字節,零,INT(S),浮點型(S),布爾)你可以直接傳遞字符串作爲DEST

但如果使用其他類型如數組,枚舉等,然後數據不能轉換爲字符串。但這也取決於驅動程序如何處理這些類型。(幾個月前作爲例子,postgres驅動程序無法處理數組,因此他總是返回[]字節,我需要通過我自己來轉換它)

+0

謝謝,現在這個目標是MySQL,所以它只是基本類型。 – Arne

+0

確保接受這個答案,如果它適合你(並且如果沒有更好的答案) – weberc2

+0

我會,但我仍然努力從掃描中的[]接口{}'轉換爲'[]字符串在寫。傳入'[]字符串到掃描沒有編譯 - 我只是沒有編輯我的答案呢。儘管如此,我的確得到了讚揚。 – Arne

0

下面的代碼嬌滴滴statisfies您的要求,您可以在https://gist.github.com/hygull/645c3dc39c69b6b69c06f5ea9deee41f得到這個代碼。表格數據也已提供。

/** 
    { 
     "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 | surendrakgadwal[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] 
*/