英文:
Is Golang's SQL package incapable of ad hoc / exploratory queries?
问题
根据文档,似乎在Go语言中从数据库中获取数据的唯一方法是使用Rows.Scan(),这意味着你必须在编译时知道所有列的数量和类型。
我是否漏掉了什么?你应该如何支持即席查询?或者如何提取可能在将来发生变化的表中的所有列?
英文:
Based of the documentation, it seems the only way to get data out of a database in Go is using Rows.Scan(), meaning you must know the count and types of all columns at compile-time.
Am I missing something? How are you supposed to support ad hoc queries? Or even pull all columns out of a table that may change in the future?
答案1
得分: 12
sql.Rows
类型有一个 Columns
方法,可以返回结果列名的列表。这可以用来确定未知查询的列数。
在 Scan
方法的文档中,有这样的说明:
> 如果参数的类型是 *[]byte,Scan 会将相应数据的副本保存在该参数中。该副本由调用者拥有,可以进行修改并持有时间不确定。可以通过使用类型为 *RawBytes 的参数来避免复制;有关其使用限制的详细信息,请参阅 RawBytes 的文档。
>
> 如果参数的类型是 *interface{},Scan 会直接复制底层驱动程序提供的值,而不进行转换。如果该值的类型是 []byte,则会进行复制,并且调用者拥有该结果。
因此,我们还支持在不知道列类型的情况下扫描列值:可以使用原始形式或 Go 类型。
将这两者结合起来,你可以使用 ...
语法调用可变参数函数 来实现以下操作:
columnNames, err := rows.Columns()
if err != nil {
log.Fatalln(err) // 或者适当的错误处理方式
}
columns := make([]interface{}, len(columnNames))
columnPointers := make([]interface{}, len(columnNames))
for i := 0; i < len(columnNames); i++ {
columnPointers[i] = &columns[i]
}
if err := rows.Scan(columnPointers...); err != nil {
log.Fatalln(err)
}
现在,columns
切片应该包含当前结果行的所有列值的解码版本。
如果你对表有额外的了解(例如,预期的类型或提前知道列数),可能可以简化逻辑。
英文:
The sql.Rows
type has a Columns
method that will give you a list of the result column names. That can be used to determine the number of columns for unknown queries.
In the docs for the Scan
method, it says:
> 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 copy can be avoided by using an
> argument of type *RawBytes instead; see the documentation for RawBytes
> for restrictions on its use.
>
> If an argument has type *interface{}, Scan copies the value provided by
> the underlying driver without conversion. If the value is of type
> []byte, a copy is made and the caller owns the result.
So we also have support for scanning column values when we don't know their type: either in their raw form, or as Go types.
Putting these two together, you could do something like the following using the ...
syntax to call variadic functions:
columnNames, err := rows.Columns()
if err != nil {
log.Fatalln(err) // or whatever error handling is appropriate
}
columns := make([]interface{}, len(columnNames))
columnPointers := make([]interface{}, len(columnNames))
for i := 0; i < len(columnNames); i++ {
columnPointers[i] = &columns[i]
}
if err := rows.Scan(columnPointers...); err != nil {
log.Fatalln(err)
}
Now the columns
slice should contain the decoded versions of all the column values for the current result row.
If you have extra knowledge about the table (e.g. expected types, or know the number of columns ahead of time), you could probably simplify the logic a little.
答案2
得分: 10
找到了一个示例代码,使用go-mssqldb驱动程序实现了这个功能。参考链接:https://github.com/denisenkom/go-mssqldb/blob/master/examples/tsql/tsql.go - 下面是提取的代码。
至少对于这个驱动程序,它是有效的。然而,它只使用了sql-namespace API,所以可能/可能也适用于其他驱动程序。
给定任何(?) SQL select 语句,它会显示结果数据行。
func exec(db *sql.DB, cmd string) error {
rows, err := db.Query(cmd)
if err != nil {
return err
}
defer rows.Close()
cols, err := rows.Columns()
if err != nil {
return err
}
if cols == nil {
return nil
}
vals := make([]interface{}, len(cols))
for i := 0; i < len(cols); i++ {
vals[i] = new(interface{})
if i != 0 {
fmt.Print("\t")
}
fmt.Print(cols[i])
}
fmt.Println()
for rows.Next() {
err = rows.Scan(vals...)
if err != nil {
fmt.Println(err)
continue
}
for i := 0; i < len(vals); i++ {
if i != 0 {
fmt.Print("\t")
}
printValue(vals[i].(*interface{}))
}
fmt.Println()
}
if rows.Err() != nil {
return rows.Err()
}
return nil
}
func printValue(pval *interface{}) {
switch v := (*pval).(type) {
case nil:
fmt.Print("NULL")
case bool:
if v {
fmt.Print("1")
} else {
fmt.Print("0")
}
case []byte:
fmt.Print(string(v))
case time.Time:
fmt.Print(v.Format("2006-01-02 15:04:05.999"))
default:
fmt.Print(v)
}
}
以上是翻译好的内容,请确认是否满意。
英文:
Found example code for go-mssqldb driver doing exactly this. Ref. https://github.com/denisenkom/go-mssqldb/blob/master/examples/tsql/tsql.go - code extracted below.
It works, at least, for this driver. However, it only uses the sql-namespace API so it will probably/possibly work for other drivers too.
Given any(?) SQL select statement, it displays the resulting data rows
func exec(db *sql.DB, cmd string) error {
rows, err := db.Query(cmd)
if err != nil {
return err
}
defer rows.Close()
cols, err := rows.Columns()
if err != nil {
return err
}
if cols == nil {
return nil
}
vals := make([]interface{}, len(cols))
for i := 0; i < len(cols); i++ {
vals[i] = new(interface{})
if i != 0 {
fmt.Print("\t")
}
fmt.Print(cols[i])
}
fmt.Println()
for rows.Next() {
err = rows.Scan(vals...)
if err != nil {
fmt.Println(err)
continue
}
for i := 0; i < len(vals); i++ {
if i != 0 {
fmt.Print("\t")
}
printValue(vals[i].(*interface{}))
}
fmt.Println()
}
if rows.Err() != nil {
return rows.Err()
}
return nil
}
func printValue(pval *interface{}) {
switch v := (*pval).(type) {
case nil:
fmt.Print("NULL")
case bool:
if v {
fmt.Print("1")
} else {
fmt.Print("0")
}
case []byte:
fmt.Print(string(v))
case time.Time:
fmt.Print(v.Format("2006-01-02 15:04:05.999"))
default:
fmt.Print(v)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论