Golang中的动态SQL选择查询

5
我正在尝试使用database/sql和mysql驱动程序构建API,该API将根据URL参数读取数据。
类似于这样的方式。
myapi.com/users?columns=id,first_name,last_name,country&sort=desc&sortColumn=last_name&limit=10&offset=20

我知道如何在定义了结构体的情况下获取所有列或仅获取特定列。但我想知道是否可以从网址中获取列,将其保存到映射中,然后只需扫描这些列,而不是预定义的结构体。
我有可行的代码,仅当列数与结构体中相同的列数时,该代码将从上述端点获取数据。如果例如删除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个回答

5
据我所知(也不是很熟悉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]
}

2

这里有一个返回动态结果集的选项,你需要一个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...)
}

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...)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接