Подтвердить что ты не робот

Прочитайте столбцы "SELECT *" в строку [] в go

Я хочу написать программу Go для выгрузки строк из таблицы базы данных в CSV файл с помощью SELECT *.

Go предоставляет отличные sql и csv apis, но csv ожидает массивы строк, а метод Scan in Rows "заполняет" поля в соответствии с их типами. Поскольку я не знаю таблицу раньше, я понятия не имею, сколько столбцов и каков их тип.

Это моя первая программа на Go, поэтому я немного борюсь.

Как мне лучше всего прочитать столбцы из экземпляра Rows в []string - и это "правильный" способ?

Спасибо!

ОБНОВИТЬ

Я все еще борюсь с параметрами. Это мой код, сейчас я использую panic вместо того, чтобы возвращать error, но я собираюсь изменить это позже. В моем тесте я os.Stdout результат запроса и 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
}
4b9b3361

Ответ 1

Чтобы прямо Scan значения в []string, вы должны создать фрагмент []interface{}, указывающий на каждую строку в вашем строчном фрагменте.

Здесь у вас есть рабочий пример для MySQL (просто измените команду sql.Open в соответствии с вашими настройками):

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

Ответ 2

чтобы получить количество столбцов (а также имена), просто используйте функцию Columns()

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

и поскольку csv может быть только строкой, просто используйте тип байта [] в качестве типа dest для Scanner. согласно документу:

Если аргумент имеет тип * [] byte, Scan сохраняет в этом аргументе копию соответствующие данные. Копия принадлежит абоненту и может быть изменен и удерживается неопределенно долго.

данные не будут преобразованы в его реальный тип. и из этого [] байта вы можете преобразовать его в строку.

если вы уверены, что ваши таблицы используют только базовые типы (string, [] byte, nil, int (s), float (s), bool), вы можете напрямую передать строку как dest

но если вы используете другие типы, такие как массивы, перечисления или так далее, то данные не могут быть преобразованы в строку. но это также зависит от того, как драйвер обрабатывает эти типы. (несколько месяцев назад, например, драйвер postgres не смог обработать массивы, поэтому он всегда возвращал [] байт, где мне нужно было преобразовать его самостоятельно)

Ответ 3

Разве это не может быть сделано вместо этого? Упрощено ниже.

var tmpErrors string

_ = row.Scan(&tmpErrors)

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

Будет ли проблема или проблема производительности, которую я не вижу?

Ответ 4

Следующий код с достаточной степенью точности отвечает вашим требованиям, вы можете получить этот код на 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 | [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]
*/