44

我想编写一个 Go 程序来将数据库表中的行转储到 csv 文件中SELECT *

Go 提供了出色的sqlcsv api,但csv需要字符串数组和根据其类型“填充”字段Scan中的方法。Rows由于我以前不知道该表,因此我不知道有多少列以及它们的类型。

这是我在 Go 中的第一个程序,所以我有点挣扎。

我如何最好地将Rows实例中的列读入[]string- 这是“正确”的方式吗?

谢谢!

更新

我仍在为参数而苦苦挣扎。这是我的代码,现在我使用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 argumentwriter.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.

更新 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
}
4

4 回答 4

52

为了Scan将值直接放入 a[]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: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)
    }
}
于 2013-01-24T11:52:50.767 回答
6

要获取列数(以及名称),只需使用 Columns() 函数

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

由于 csv 只能是字符串,因此只需使用 []byte 类型作为 Scanner 的 dest 类型。根据文档:

如果参数的类型为 *[]byte,Scan 会在该参数中保存相应数据的副本。副本归调用者所有,可以无限期修改和持有。

数据不会被转换成它的真实类型。然后您可以从此 []byte 将其转换为字符串。

如果您确定您的表仅使用基本类型(字符串、[]byte、nil、int(s)、float(s)、bool),您可以直接将字符串作为 dest 传递

但如果您使用其他类型,如数组、枚举等,则无法将数据转换为字符串。但这也取决于驱动程序如何处理这些类型。(例如几个月前,postgres 驱动程序无法处理数组,所以他总是返回 []byte 我需要自己转换它的地方)

于 2013-01-23T13:02:36.457 回答
0

以下代码很好地满足了您的要求,您可以在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   | rishikesh0014051992@gmail.com | 917353787704 |
|  2 | Sandeep   | E        | sandeep  | sandeepeswar8@gmail.com       | 919739040038 |
|  3 | Darshan   | Sidar    | darshan  | sidardarshan@gmail.com        | 917996917565 |
|  4 | Surendra  | Prajapat | surendra | surendrakgadwal@gmail.com     | 918385894407 |
|  5 | Mukesh    | Jakhar   | mukesh   | mjakhar.kjahhar@gmail.com     | 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]
*/
于 2017-05-26T12:04:20.460 回答
-1

不能这样做吗?简化如下。

var tmpErrors string

_ = row.Scan(&tmpErrors)

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

会有我没有看到的问题或性能问题吗?

于 2019-04-29T20:18:08.640 回答