Golang: Store Query Result in a Map

目录

  • [1. Golang: Store Query Result in a Map](#1. Golang: Store Query Result in a Map)
    • [1.1. Using Structs](#1.1. Using Structs)
    • [1.2. Using Maps](#1.2. Using Maps)

1. Golang: Store Query Result in a Map

注意: 使用这个可能会造成列名和列值乱串的现象,解决这个可以使用 AS 语法:

sql 复制代码
SELECT 
  TENANT_ID AS TENANT_ID,
  SVR_IP AS SVR_IP,
  SVR_PORT AS SVR_PORT,
  PLAN_ID AS PLAN_ID,
  SQL_ID AS SQL_ID,
  TYPE AS TYPE,
  DB_ID AS DB_ID,
  STATEMENT AS STATEMENT,
  PLAN_HASH AS PLAN_HASH,
  LAST_ACTIVE_TIME AS LAST_ACTIVE_TIME,
  ELAPSED_TIME AS ELAPSED_TIME
FROM GV$PLAN_CACHE_PLAN_STAT
WHERE LAST_ACTIVE_TIME > '%s' AND LAST_ACTIVE_TIME <= '%s' AND ELAPSED_TIME > %d

Converting the results of a SQL query to a struct in Go is trivial, but sometimes you don't know ahead of time exactly what columns and types you're going to be retrieving, and a map may be better suited for storing the results.

1.1. Using Structs

First, here's the standard way we can convert results to a struct:

go 复制代码
rows, _ := db.Query("SELECT ...") // Note: Ignoring errors for brevity
for rows.Next() {
    s := myStruct{}
    if err := rows.Scan(&s); err != nil {
        return err
    }
    
    // Do something with 's'
}

Easy enough, but storing your query result in a map is a bit trickier.

1.2. Using Maps

Let's say for example you're working with a user's database where you don't know the schema ahead of time. You can't write a struct to store the results, because you don't know what columns and data types you're going to be retrieving. What we want in this case is a map[string]interface{} where the key is the column name and the value could be any data type.

You might assume you can do the following:

go 复制代码
rows, _ := db.Query("SELECT ...") // Note: Ignoring errors for brevity
for rows.Next() {
    m := make(map[string]interface{})
    
    // This WON'T WORK
    if err := rows.Scan(&m); err != nil {
        // ERROR: sql: expected X destination arguments in Scan, not 1
    }
}

Basically the SQL package is thinking that you expect a single column to be returned and for it to be a map[string]interface{} compatible type, which isn't what we we're trying to do.

Instead what we have to do in order to make this work is the following:

go 复制代码
rows, _ := db.Query("SELECT ...") // Note: Ignoring errors for brevity
cols, _ := rows.Columns()

for rows.Next() {
    // Create a slice of interface{}'s to represent each column,
    // and a second slice to contain pointers to each item in the columns slice.
    columns := make([]interface{}, len(cols))
    columnPointers := make([]interface{}, len(cols))
    for i, _ := range columns {
        columnPointers[i] = &columns[i]
    }
    
    // Scan the result into the column pointers...
    if err := rows.Scan(columnPointers...); err != nil {
        return err
    }

    // Create our map, and retrieve the value for each column from the pointers slice,
    // storing it in the map with the name of the column as the key.
    m := make(map[string]interface{})
    for i, colName := range cols {
        val := columnPointers[i].(*interface{})
        m[colName] = *val
    }
    
    // Outputs: map[columnName:value columnName2:value2 columnName3:value3 ...] 
    fmt.Print(m)
}

So how does this work? Well first we query and get our rows as usual, but this time we use rows.Columns() to get a reference to all column names in the result.

Then for each row, we create a slice of interface{}'s called columns who's length matches the number of columns. Next we create a second slice with the same length called columnPointers, but this time we iterate over each element in columns and store a pointer to the interface{} element in our columnPointers slice. This is necessary because the sql package requires pointers when scanning. So now we have two slices, one of interface{}s and one of pointers to the interface{}s.

Now we can scan the row into the slice of interface{} pointers (ie. columnPointers).

Finally, we create our map[string]interface{}, and iterate over the column names. For each column name (colName), we deference the interface{} pointer at the current loop index from the columnPointers slice, which references the value in the columns slice. We take this dereferenced value and store it in the map as the value, with the key being the column name.

Now we can use the map however we need, essentially allowing us to perform queries dynamically, without requiring knowledge of the schema we're going to be querying when we write our code.

This may seem a little confusing at first, especially if you have no prior experience with pointers. If so, I'd recommend reading up on Pointers and trying the code out, inserting some debug logging to fully understand what's happening during each step of the process.

相关推荐
会飞的爱迪生3 分钟前
mysql主从同步延迟原因分析
数据库·mysql
席万里5 分钟前
InnoDB存储引擎【MySQL从放弃到入门】
数据库·mysql
lcx_defender14 分钟前
【Docker】部署MySQL容器
mysql·docker·容器
木易小熙44 分钟前
MySQL最左匹配原则是什么
数据库·mysql
Anna_Tong1 小时前
全局流量管理:提升用户体验与保障服务稳定性
运维·服务器·网络·数据库·安全·负载均衡
番茄电脑全能王1 小时前
《鸣潮》运行时提示找不到emp.dll是什么原因?缺失emp.dll文件要怎么解决?
网络·数据库·经验分享·游戏·电脑
苹果酱05672 小时前
Golang的文件解压技术研究与应用案例
java·vue.js·spring boot·mysql·课程设计
hu_yuchen2 小时前
从单机到微服务的转型之路
redis·mysql·微服务
一只淡水鱼662 小时前
【mybatis】基本操作:详解Spring通过注解和XML的方式来操作mybatis
java·数据库·spring·mybatis
张声录12 小时前
【ETCD】【实操篇(十六)】基于角色的访问控制:ETCD 安全管理指南
数据库·安全·etcd