【问题标题】:Dynamic SQL select query in GolangGolang 中的动态 SQL 选择查询
【发布时间】:2019-08-17 11:35:43
【问题描述】:

我正在尝试使用 database/sql 和 mysql 驱动程序构建 API,它将根据 URL 参数读取数据。
像这样的

myapi.com/users?columns=id,first_name,last_name,country&sort=desc&sortColumn=last_name&limit=10&offset=20

我知道在 struct 中定义时如何获取所有列或仅获取特定列。但我想知道是否可以从 url 获取列,而不是预定义的结构将其保存到映射,而不仅仅是扫描这些列。
我有工作代码,只有当列数与结构相同时,才会从上面的端点获取数据。例如,如果我删除 country,我会收到错误,Scan 需要 4 个参数,但给出了 3 个。

我不需要具体的代码,只需要一些指导,因为我正在学习 Go,我的背景是 PHP,这更容易做到。

更新

感谢答案,我有部分可行的解决方案。
这是代码:

cols := []string{"id", "first_name", "last_name"}
vals := make([]interface{}, len(cols))
w := map[string]interface{}{"id": 105}

var whereVal []interface{}
var whereCol []string

for k, v := range w {
    whereVal = append(whereVal, v)
    whereCol = append(whereCol, fmt.Sprintf("%s = ?", k))
}

for i := range cols {
    vals[i] = new(interface{})
}
err := db.QueryRow("SELECT "+strings.Join(cols, ",")+" FROM users WHERE "+strings.Join(whereCol, " AND "), whereVal...).Scan(vals...)

if err != nil {
    fmt.Println(err)
}

b, _ := json.Marshal(vals)
fmt.Println(string(b))

这应该查询SELECT id, first_name, last_name FROM users WHERE id = 105;

但是如何将数据输出到正确的 json 对象?现在它会像这样打印出用 base64 编码的字符串。

[105,"Sm9obm55","QnJhdm8="]

【问题讨论】:

    标签: mysql sql go


    【解决方案1】:

    这是一个发现返回动态结果集的选项,您需要一个 interface{} 数组,但您必须分配一个 new(interface{}) 以获取可以通过 Scan 方法写入的指针

    //...
    types, _ := rows.ColumnTypes()  
    for rows.Next() {
     row := make([]interface{}, len(types))
     for i := range types {
        row[i] = new(interface{})
     }
     rows.Scan(row...)
    }
    

    【讨论】:

      【解决方案2】:

      据我所知(在 Go 中也没有太多经验),如果你不为 value 分配一个真正的类型,那么 Scan 将返回 []byte 并且当它被编组时它返回 base64 编码的字符串。

      所以你必须为你的列分配一个类型,如果你想要正确的 json,然后将键分配给值。

      在您的示例中,可以这样做:

      cols := []string{"id", "first_name", "last_name"}
      vals := make([]interface{}, len(cols))
      result := make(map[string]interface{}, len(cols))
      
      for i, key := range cols {
          switch key {
          case "id", "status":
              vals[i] = new(int)
          default:
              vals[i] = new(string)
          }
      
          result[key] = vals[i]
      }
      
      b, _ := json.Marshal(result)
      fmt.Println(string(b))
      

      因此,现在我们不是循环遍历 cols 并为每列创建新接口,而是创建键/值对并根据列名分配类型。

      另外,如果表中有可为空的列,并且您可能有,那么您将收到错误,因为nil 无法进入string。所以我建议这个包gopkg.in/guregu/null.v3,然后分配像null.String这样的类型。这样,您将返回 null 作为值。

      例如:

      for i, key := range cols {
          switch key {
          case "id", "status":
              vals[i] = new(int)
          case "updated_at", "created_at":
              vals[i] = new(null.Time)
          default:
              vals[i] = new(null.String)
          }
      
          result[key] = vals[i]
      }
      

      【讨论】:

        【解决方案3】:

        我会创建一个带有动态字段的查询语句(使用占位符来避免 sql 注入):

        rows := db.QueryRow("SELECT {{YOUR_FIELDS}} from table_tbl")
        

        创建列大小相同的可变载体

        vals := make([]interface{}, len(rows.Columns()))
        

        如果不需要类型检查或不知道字段类型,则使用 sql.RawBytes 作为字段类型,否则使用相同类型的字段。

        for i, _ := range cols {
            vals[i] = new(sql.RawBytes)
            //check column name, if it is id, and you know it is integer
            //vals[i] = new(int)
        }
        

        迭代行并扫描

        for rows.Next() {
            err = rows.Scan(vals...)
        }
        

        【讨论】:

          【解决方案4】:

          您必须先获取结果列数,然后不要超过大小。

          如果你的意思是查询字段,你需要动态创建查询字符串,参数大小必须相同。

          【讨论】:

            猜你喜欢
            • 2018-02-22
            • 2012-01-17
            • 2021-07-10
            • 1970-01-01
            • 1970-01-01
            • 2019-06-10
            • 2016-09-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多