在Go中将”SELECT *”列读入[]string中

huangapple go评论95阅读模式
英文:

read "SELECT *" columns into []string in go

问题

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

Go提供了优秀的sqlcsv API,但是csv期望字符串数组,并且Rows中的Scan方法根据字段类型来“填充”字段。
由于我不知道表的结构,我不知道有多少列以及它们的类型。

这是我在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 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)
        }
        // ... 转换?
        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
}
英文:

I want to write a Go program to dump rows from a database table into a csv file using SELECT *.

Go provides the excellent sql and csv apis, but csv expects arrays of strings and the Scan method in Rows "fills" fields according to their types.
As I don't know the table before, I have no idea how many columns there are and what their types are.

It's my first program in Go, so I'm struggling a little.

How do I best read the columns from a Rows instance into a []string - and is that the "right" way?

Thanks!

UPDATE

I'm still struggling with the parameters. This is my code, for now I'm using panic instead of returning an error, but I'm going to change that later. In my test, I'm passing the query result and 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
}

For this, I get cannot use cols (type []string) as type []interface {} in function argument (at the writer.Write(cols) line.

I then tested

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

which lead to panic: sql: Scan error on column index 0: destination not a pointer.

UPDATE 2

I independently arrived at ANisus' solution. This is the code I'm using now.

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
}

答案1

得分: 53

为了直接将值扫描到一个[]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: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)
	}
}
英文:

In order to directly Scan the values into a []string, you must create an []interface{} slice pointing to each string in your string slice.

Here you have a working example for MySQL (just change the sql.Open-command to match your settings):

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

答案2

得分: 6

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

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

由于csv只能是字符串,所以将[]byte类型作为Scanner的目标类型。
根据文档:

> 如果参数的类型为*[]byte,则Scan将在该参数中保存相应数据的副本。副本归调用者所有,可以进行修改并无限期保留。

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

如果您确定表只使用基本类型(字符串、[]byte、nil、int(s)、float(s)、bool),则可以直接传递字符串作为目标。

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

英文:

to get the Number of Columns (and also the names) just use the Columns() Function

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

and as csv can only be a strings, just use a []byte type as dest type for Scanner.
according to docu:

> 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.

the data will not be transformed into its real type.
and from this []byte you can then convert it to string.

if your are sure your tables only use base types (string, []byte, nil, int(s), float(s), bool) you can directly pass string as dest

but if you use other types like arrays, enums, or so on, then the data cant be transformed to string. but this also depends how the driver handles this types. (some months ago as example, the postgres driver was not able to handle arrays, so he returned always []byte where i needed to transform it by my own)

答案3

得分: 0

以下是翻译好的代码部分:

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]
英文:

The following code prettily statisfies your requirement, you can get this code at https://gist.github.com/hygull/645c3dc39c69b6b69c06f5ea9deee41f. The table data has been also provided.

/**
{
&quot;created_on&quot;: &quot;26 may 2017&quot;,
&quot;todos&quot;: [
&quot;go get github.com/go-sql-driver/mysql&quot;		
],
&quot;aim&quot;: &quot;Reading fname column into []string(slice of strings)&quot;
}
*/
/* 
mysql&gt; 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&gt; 
*/
package main
import &quot;fmt&quot;
import &quot;log&quot;
import (
_&quot;github.com/go-sql-driver/mysql&quot;	
&quot;database/sql&quot;
)
func main() {
// db, err := sql.Open(&quot;mysql&quot;, &quot;&lt;username&gt;:&lt;password&gt;@tcp(127.0.0.1:&lt;port&gt;)/&lt;dbname&gt;?charset=utf8&quot;	)
db, err := sql.Open(&quot;mysql&quot;, &quot;hygull:admin@67@tcp(127.0.0.1:3306)/practice_db?charset=utf8&quot;)
if err != nil {
log.Fatal(err)
}
rows, err := db.Query(&quot;select fname from users&quot;)
if err != nil {
log.Fatal(err)
}
firstnames := []string{}
for rows.Next() {
var fname string
rows.Scan(&amp;fname)
firstnames = append(firstnames, fname)
}
fmt.Println(firstnames)
db.Close()
}
/* 
[Rishikesh Sandeep Darshan Surendra Mukesh]
*/

答案4

得分: -1

这样做可以吗?简化如下。

var tmpErrors string
_ = row.Scan(&amp;tmpErrors)
actualVarHere := strings.Split(tmpErrors, &quot;\n&quot;)

我是否没有注意到问题或性能问题?

英文:

Couldn't this be done instead? Simplified below.

var tmpErrors string
_ = row.Scan(&amp;tmpErrors)
actualVarHere := strings.Split(tmpErrors, &quot;\n&quot;)

Would there be a problem or performance issue I am not seeing?

huangapple
  • 本文由 发表于 2013年1月23日 18:54:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/14477941.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定