Golang中的动态SQL选择查询

时间:2019-03-27 00:30:23

标签: mysql sql go

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

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

在struct中定义时,我知道如何获取所有列或仅获取特定列。但是我想知道是否有可能从url中获取列,而不是将预定义的结构保存到映射中,而不是仅扫描这些列。
我有工作代码,仅当列数与struct中相同时,才会从端点上方获取数据。例如,如果我删除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="]

4 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:1)

我将使用动态字段创建查询语句(使用占位符以避免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...)
}

答案 2 :(得分:1)

据我所知(在Go中也没有很多经验),如果您没有为值分配实型,则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 :(得分:0)

这里有一个发现返回动态结果集的选项,您将需要一个interface {}数组,但是您必须分配一个新的(interface {})才能获得可由Scan方法写入的指针

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