Go查询CH 数据 tuple 转数组
Go 查询 ClickHouse
业务场景:需要使用 Golang
从 CilckHouse
集群中查询数据,查询的 SQL
以模版的形式存放在 Postgresql
数据库中.
使用的 driver
是 sqlx
- 使用CH 在私有集群中,报漏的端口只有
http
的 8123
所以需要更改拨号协议
- 另外如果
ClickHouse
是必要的依赖服务,每次获取查询对象 需要先检查一下 Ping()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| type ClickHouse struct { Host string Port int User string Password string DBName string ReadTimeout uint WriteTimeout uint Parameters string }
func (a ClickHouse) DSN() string { return fmt.Sprintf("http://%s:%s@%s:%d/%s", a.User, a.Password, a.Host, a.Port, a.DBName) }
connect, err := sqlx.Open("clickhouse", "tcp://127.0.0.1:9000?compress=true&debug=true")
checkErr(err) if err := connect.Ping(); err != nil { if exception, ok := err.(*clickhouse.Exception); ok { fmt.Printf("[%d] %s \n%s\n", exception.Code, exception.Message, exception.StackTrace) } else { fmt.Println(err) } return }
|
Row 转 Map
- 因为查询的 SQL 模版较多, 不想定义对应的结构体 :smaile:
所以对查询数据做了一个 Row2Map 的处理,这样获取到的数据 直接转换为 JSON 输出到前端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| type QueryResult map[string]interface{}
func RowsToMap(rows *sqlx.Rows) []QueryResult { var list = []QueryResult{}
if rows == nil { return list } ts, _ := rows.ColumnTypes() columns, _ := rows.Columns() columnLength := len(columns) cache := make([]interface{}, columnLength) for index, _ := range cache { var a interface{} cache[index] = &a } for rows.Next() { _ = rows.Scan(cache...) item := make(QueryResult) for idex, data := range cache { val := *data.(*interface{}) item[columns[idex]] = GenarateTuplesToArray(val, ts[idex]) } list = append(list, item) } return list }
|
Tuple 转 数组
- 在返回的数据中出现 前端 Javascrip 取值异常的情况,因为先前的数据接口已经有一版本 nodejs 完成的.
所以前端的 数据处理部分已经是完成了的.现在出现了出局结构不一致的情况,具体数据不同体现在:
1 2 3
| [{"11111","asdasd,aaaa,aaa"}] => nodejs => [["11111","asdasd,aaaa,aaa"]]
[{"11111","asdasd,aaaa,aaa"}] => golang => [{field0:"11111",field1:"asdasd,aaaa,aaa"}]
|
最初以为是 SQL 变更了: 排查后发现并没有修改 SQL.但是发现了查询有使用 Tuple
具体对比数据数据结构后 确认是 由于 node/go 转换元组 Tuple
的操作不同产生的
1 2 3 4 5 6 7 8 9
| // anwsers 字段的查询Tuple arrayFilter( (i,type) -> (type IN (1, 28)), arrayMap(rdata -> ( rdata, concat(dictGet('ipip4', 'country', tuple(IPv4StringToNum(rdata))), If(province = '','',','),dictGet('ipip4','province',tuple(IPv4StringToNum(rdata))) AS province, If(city = '','',','),dictGet('ipip4','city',tuple(IPv4StringToNum(rdata))) AS city)), res.`responseAnswerRrs.rdata`), res.`responseAnswerRrs.type`) AS answers
|
打印详细的查询结果 及 元组字段 ‘anwsers’ 格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| ts, _ := rows.ColumnTypes()
columnTypes := ts[idex]
columnTypes.DatabaseTypeName() = "Array(Tuple(String, String))" columnTypes.ScanType() = []struct { Field0 string Field1 string }
type ColumnType struct { name string
hasNullable bool hasLength bool hasPrecisionScale bool
nullable bool length int64 databaseType string precision int64 scale int64 scanType reflect.Type }
[{"11111","asdasd,aaaa,aaa"}]
|
最后解决方案是: 修改 row2map 函数,将 Array(Tuple(String, String))
手动转换为 [][]string
,
这样 json.Marshal(d)
处理的时候就会保持原来的数组格式
[{"11111","asdasd,aaaa,aaa"}] => golang => [["11111","asdasd,aaaa,aaa"]]
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func GenarateTuplesToArray(val interface{}, columnTypes *sql.ColumnType) interface{} { if columnTypes.DatabaseTypeName() == "Array(Tuple(String, String))" { d := val.([]struct { Field0 string Field1 string }) t := make([][]string, len(d)) for k, v := range d { t[k] = []string{v.Field0, v.Field1} } return t } return val }
|