【问题标题】:Creating an array/slice to store DB Query results in Golang在 Golang 中创建一个数组/切片来存储数据库查询结果
【发布时间】:2019-05-24 20:36:28
【问题描述】:

我刚刚开始使用 golang,我正在尝试从 Postgres users 表中读取几行并将结果存储为对行建模的 User 结构数组。

type User struct {
    Id  int
    Title string
}

func Find_users(db *sql.DB) {
    // Query the DB
    rows, err := db.Query(`SELECT u.id, u.title FROM users u;`)
    if err != nil { log.Fatal(err) }

    // Initialize array slice of all users. What size do I use here? 
    // I don't know the number of results beforehand
    var users = make([]User, ????)

    // Loop through each result record, creating a User struct for each row
    defer rows.Close()
    for i := 0; rows.Next(); i++ {
        err := rows.Scan(&id, &title)
        if err != nil { log.Fatal(err) }

        log.Println(id, title)
        users[i] = User{Id: id, Title: title}
    }

    // .... do stuff
}

如您所见,我的问题是我想预先初始化一个数组或切片来存储所有数据库记录,但我不知道提前会有多少记录。

我正在权衡几种不同的方法,并想找出以下哪些方法在 golang 社区中最常用 -

  1. 事先创建一个非常大的数组(例如 10,000 个元素)。好像很浪费

  2. 事先明确计算行数。这可能有效,但我需要运行 2 个查询 - 一个用于计数,一个用于获取结果。如果我的查询很复杂(此处未显示),那就是在 2 个地方重复该逻辑。或者,我可以两次运行相同的查询,但首先遍历它并计算行数。这一切都会奏效,但它似乎不干净。

  3. 我见过扩展切片的示例。我不太了解切片,无法在这里进行调整。另外,如果我不断地将切片扩展 10k 次,那肯定看起来很浪费。

谢谢!

【问题讨论】:

    标签: postgresql go


    【解决方案1】:

    Go 有一个内置的 append 函数正是为了这个目的。它接受一个切片和一个或多个元素,并将这些元素附加到切片,返回新切片。此外,切片 (nil) 的零值是长度为零的切片,因此如果附加到零切片,它将起作用。因此,您可以这样做:

    type User struct {
        Id    int
        Title string
    }
    
    func Find_users(db *sql.DB) {
        // Query the DB
        rows, err := db.Query(`SELECT u.id, u.title FROM users u;`)
        if err != nil {
            log.Fatal(err)
        }
        defer rows.Close()
    
        var users []User
        for rows.Next() {
            err := rows.Scan(&id, &title)
            if err != nil {
                log.Fatal(err)
            }
    
            log.Println(id, title)
            users = append(users, User{Id: id, Title: title})
        }
        if err := rows.Err(); err != nil {
            log.Fatal(err)
        }
    
        // ...
    }
    

    【讨论】:

    • 谢谢!为了我自己的教育——这有道理,但这不是低效的操作吗?如果我的数据库有数千条记录,它将扩展/附加到切片数千次。我知道修改切片本身是一种快速操作,因为它只是对底层数组的固定引用,但它仍然需要将底层数组扩展数千次。
    • 每次重新分配时,有效执行此操作的标准算法(我假设append 使用了一些变体)是底层数组长度的两倍。这样,对于 n 追加操作,您只执行 log(n) 分配,并且分配大约 2n 内存。因此,您分配的空间量与追加操作的数量成线性关系。
    • 啊,是的,这看起来确实更有效率。我将您的答案标记为已接受,非常感谢:)
    • @joshlf 您能否进一步详细说明该评论?
    • @Chris 对加倍算法有一个很好的解释 - 以及为什么附加许多项目会更有效 - 在这篇文章中:interviewcake.com/concept/java/dynamic-array-amortized-analysis
    【解决方案2】:

    用户附加到切片:

    type DeviceInfo struct {
         DeviceName     string
         DeviceID       string
         DeviceUsername string
         Token          string
    }
    
    func QueryMultiple(db *sql.DB){
        var device DeviceInfo
        sqlStatement := `SELECT "deviceName", "deviceID", "deviceUsername", 
                     token FROM devices LIMIT 10`
        rows, err := db.Query(sqlStatement)
        if err != nil {
           panic(err)
        }
        defer rows.Close()
        var deviceSlice []DeviceInfo
        for rows.Next(){
            rows.Scan(&device.DeviceID, &device.DeviceUsername, &device.Token, 
                      &device.DeviceName)
             deviceSlice = append(deviceSlice, device)
        }
        fmt.Println(deviceSlice)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-09-30
      • 1970-01-01
      • 2021-05-12
      • 2013-11-20
      • 2012-06-12
      • 2020-03-09
      • 1970-01-01
      • 2014-12-19
      相关资源
      最近更新 更多