【问题标题】:How to append last sql row to a list without replacing previous rows in Golang如何将最后一个 sql 行附加到列表而不替换 Golang 中的先前行
【发布时间】:2018-04-28 09:46:47
【问题描述】:

此代码提供了 AFAIK 正确的 JSON 输出 [{},{}],但每一行都被追加并替换所有先前的行,因此结果仅显示最后一行的副本。

var rows *sql.Rows
rows, err = db.Query(query)
cols, _ := rows.Columns()
colnames, _ := rows.Columns()
vals := make([]interface{}, len(cols))

for i, _ := range cols {
   vals[i] = &cols[i]
}

m := make(map[string]interface{})
 
for i, val := range vals {
  m[colnames[i]] = val
}
    
list := make([]map[string]interface{}, 0)
for rows.Next() {
err = rows.Scan(vals...)
   list = append(list, m)
}
json, _ := json.Marshal(list)
fmt.Fprintf(w,"%s\n", json)

这是在幕后循环遍历行的情况:

循环 1:{“ID”:“1”,“NAME”:“约翰 }

循环 2:{“ID”:“2”,“NAME”:“Jane Doe”}{“ID”:“2”,“NAME”:“Jane Doe”}

循环 3:{“ID”:“3”,“NAME”:“唐老鸭”}{“ID”:“3”,“NAME”:“唐老鸭”}{“ID”:“3” ,“名字”:“唐老鸭”}

rows.Scan 获取正确的值,但它附加并替换所有以前的值。

最终的输出是这样的

[{“ID”:“3”,“NAME”:“唐老鸭”},{“ID”:“3”,“NAME”:“唐老鸭”},{“ID”:“3” ,“名称”: “唐老鸭”}]

但应该是这样的:

[{“ID”:“1”,“NAME”:“John Doe”},{“ID”:“2”,“NAME”:“Jane Doe”},{“ID”:“3” ,“名称”: “唐老鸭”}]

我做错了什么?

您可以对此投反对票,但请解释原因。我还是 Golang 的新手,想学习。

【问题讨论】:

  • 您在不修改 m 的情况下一遍又一遍地将 m 添加到列表中。为什么你期望切片元素之后会有所不同?
  • 哦,我现在明白了。您似乎认为 m 以某种方式被复制。那是a)不正确,b)不会改变任何东西,因为地图存储了指针。所以复制地图会复制指针,但它们仍将指向相同的值。但是 Scan 改变了指向的值,而不是指针;地址不变。长话短说,make 每行都有一张新地图。

标签: go


【解决方案1】:

我修复了它并与 cmets 解释你做错了什么:

// 1. Query
var rows *sql.Rows
rows, err = db.Query(query)
cols, _ := rows.Columns()

// 2. Iterate
list := make([]map[string]interface{}, 0)
for rows.Next() {
    vals := make([]interface{}, len(cols))
    for i, _ := range cols {
        // Previously you assigned vals[i] a pointer to a column name cols[i].
        // This meant that everytime you did rows.Scan(vals),
        // rows.Scan would see pointers to cols and modify them
        // Since cols are the same for all rows, they shouldn't be modified.

        // Here we assign a pointer to an empty string to vals[i],
        // so rows.Scan can fill it.
        var s string
        vals[i] = &s

        // This is effectively like saying:
        // var string1, string2 string
        // rows.Scan(&string1, &string2)
        // Except the above only scans two string columns
        // and we allow as many string columns as the query returned us — len(cols).
    }

    err = rows.Scan(vals...)

    // Don't forget to check errors.
    if err != nil {
        log.Fatal(err)
    }

    // Make a new map before appending it.
    // Remember maps aren't copied by value, so if we declared
    // the map m outside of the rows.Next() loop, we would be appending
    // and modifying the same map for each row, so all rows in list would look the same.
    m := make(map[string]interface{})
    for i, val := range vals {
        m[cols[i]] = val
    }
    list = append(list, m)
}

// 3. Print.
b, _ := json.MarshalIndent(list, "", "\t")
fmt.Printf("%s\n", b)

别担心,当我还是初学者的时候,这对我来说也很难理解。

现在,有趣的事情:

var list []map[string]interface{}
rows, err := db.Queryx(query)
for rows.Next() {
    row := make(map[string]interface{})
    err = rows.MapScan(row)
    if err != nil {
      log.Fatal(err)
    }
    list = append(list, row)
}

b, _ := json.MarshalIndent(list, "", "\t")
fmt.Printf("%s\n", b)

这与上面的代码相同,但使用sqlx。简单一点,不是吗?

sqlx 是在 database/sql 之上的扩展,具有将行直接扫描到映射和结构的方法,因此您不必手动执行此操作。

我认为你的模型作为结构体看起来更好:

type Person struct {
    ID int
    Name string
}

var people []Person
rows, err := db.Queryx(query)
for rows.Next() {
    var p Person
    err = rows.StructScan(&p)
    if err != nil {
        log.Fatal(err)
    }
    people = append(people, p)
}

你不觉得吗?

【讨论】:

  • 谢谢!它工作完美。我知道 sqlx,但没有修复 base64 到字符串的转换。数字显示不正确。使用结构是最后的手段,因为我经常不知道结构。
  • 这似乎是个问题。此代码无法处理 NULL 值。它崩溃了。我该如何解决这个问题?
  • 尝试用vals[i] = &&s 替换vals[i] = &s,但它可能只适用于可为空的列,我不确定。
  • 我的错,不带 & 试试,把前面的行改成var s **string
  • @moshe-revah 一切都崩溃了。甚至不是NULL
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-08
  • 2016-11-12
  • 2015-07-12
  • 2021-07-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多