内容目录
Go语言查询数据库
最近在用Go语言
查询数据库的时候发现非常麻烦,我们先来看下官方文档给出的例子:
var (
id int
name string
)
rows, err := db.Query("select id, name from users where id = ?", 1)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&id, &name)
if err != nil {
log.Fatal(err)
}
log.Println(id, name)
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
麻烦在哪里:
- 需要先定义好接收变量,需要指明正确的类型
rows.Scan
的时候需要传递第一步定义的变量的地址- 使用结果的时候无法在循环中进行,需要一个一个的调用,比如上面的
log.Println(id, name)
好,这么多问题,肯定有人给出了其他的解决方法,不用这么麻烦的对吧,还是看官方文档,这里给出了一个比较好的解决方法:
cols, err := rows.Columns()
if err != nil {
// handle the error
} else {
dest := []interface{}{ // Standard MySQL columns
new(uint64), // id
new(string), // host
new(string), // user
new(string), // db
new(string), // command
new(uint32), // time
new(string), // state
new(string), // info
}
if len(cols) == 11 {
// Percona Server
} else if len(cols) > 8 {
// Handle this case
}
err = rows.Scan(dest...)
// Work with the values in dest
}
然后呢,如果处理这个dest
,官方文档竟然省略了,好吧,我们就自己来处理一下。
可以发现,这个dest是一个指针数组,我们需要拿到其里面的值,也就是做一个转换,从指针数组转成值数组,下面的代码演示了一个综合案例:
func TestQueryMySql(t *testing.T) {
rows, err := db.Query("select id, apkMd5, apkSize, apkUrl, cpName, createTime, endTime, packageName, softId, type, updateTime, versionCode, versionName from advert_cp")
if err != nil {
log.Fatalln(err)
}
var rowValues [][]interface{}
dest := []interface{}{ // Standard MySQL columns
new(uint64), // id
new(string), // apkMd5
new(uint64), // apkSize
new(string), // apkUrl
new(string), // cpName
new(time.Time), // createTime
new(sql.NullTime), // endTime
new(string), // packageName
new(sql.NullInt64), // softId
new(string), // type
new(sql.NullTime), // updateTime
new(uint64), // versionCode
new(string), // versionName
}
for rows.Next() {
rows.Scan(dest...)
rowValues = append(rowValues, destValues(dest))
}
columns := []string{"id", "apkMd5", "apkSize", "apkUrl", "cpName", "createTime", "endTime", "packageName", "softId", "type", "updateTime", "versionCode", "versionName"}
util.ExportExcel("广告数据", columns, rowValues)
}
// destValues 转换指针数组为值数组
func destValues(dest []interface{}) []interface{} {
values := make([]interface{}, len(dest))
for index, value := range dest {
var x interface{}
switch vt := value.(type) {
// 解析sql.NullXXX的值
case driver.Valuer:
if v, err := vt.Value(); err == nil {
x = v
} else {
x = nil
}
case *string:
x = *vt
case *int64:
x = *vt
case *uint64:
x = *vt
case *time.Time:
x = *vt
default:
x = vt
}
values[index] = x
}
return values
}
说下上面的代码:
- 可以重复使用
dest指针数组
destValues
中可以加入自己的逻辑,比如做格式转换,解析sql.NullXXX的值等,日期格式化等- 导出到Excel,下面我就给出导出的代码
导出Excel
导出Excel基本都是用的360开发的Excelize库,这个库是Go 开源项目中唯一支持复杂样式 XLSX 文件的类库,可应用于各种报表系统中。
他提供了很方便的API,可以直接对单元格进行读写。然而在常用的导出中,还是需要写大量的代码,这里我封装了一个简单的工具类
package util
import (
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
"strconv"
)
// maxCharCount 最多26个字符A-Z
const maxCharCount = 26
// ExportExcel 导出Excel文件
// sheetName 工作表名称
// columns 列名切片
// rows 数据切片,是一个二维数组
func ExportExcel(sheetName string, columns []string, rows [][]interface{}) {
f := excelize.NewFile()
sheetIndex := f.NewSheet(sheetName)
f.DeleteSheet("Sheet1")
maxColumnRowNameLen := 1 + len(strconv.Itoa(len(rows)))
columnCount := len(columns)
if columnCount > maxCharCount {
maxColumnRowNameLen++
} else if columnCount > maxCharCount*maxCharCount {
maxColumnRowNameLen += 2
}
columnNames := make([][]byte, 0, columnCount)
for i, column := range columns {
columnName := getColumnName(i, maxColumnRowNameLen)
columnNames = append(columnNames, columnName)
// 首行
f.SetCellValue(sheetName, getColumnRowName(columnName, 1), column)
}
for rowIndex, row := range rows {
for columnIndex, columnName := range columnNames {
// 从第二行开始
f.SetCellValue(sheetName, getColumnRowName(columnName, rowIndex+2), row[columnIndex])
}
}
f.SetActiveSheet(sheetIndex)
// Save xlsx file by the given path.
err := f.SaveAs("./Book1.xlsx")
if err != nil {
fmt.Println(err)
}
}
// getColumnName 生成列名
// Excel的列名规则是从A-Z往后排;超过Z以后用两个字母表示,比如AA,AB,AC;两个字母不够以后用三个字母表示,比如AAA,AAB,AAC
// 这里做数字到列名的映射:0 -> A, 1 -> B, 2 -> C
// maxColumnRowNameLen 表示名称框的最大长度,假设数据是10行,1000列,则最后一个名称框是J1000(如果有表头,则是J1001),是4位
// 这里根据 maxColumnRowNameLen 生成切片,后面生成名称框的时候可以复用这个切面,而无需扩容
func getColumnName(column, maxColumnRowNameLen int) []byte {
const A = 'A'
if column < maxCharCount {
// 第一次就分配好切片的容量
slice := make([]byte, 0, maxColumnRowNameLen)
return append(slice, byte(A+column))
} else {
// 递归生成类似AA,AB,AAA,AAB这种形式的列名
return append(getColumnName(column/maxCharCount-1, maxColumnRowNameLen), byte(A+column%maxCharCount))
}
}
// getColumnRowName 生成名称框
// Excel的名称框是用A1,A2,B1,B2来表示的,这里需要传入前一步生成的列名切片,然后直接加上行索引来生成名称框,就无需每次分配内存
func getColumnRowName(columnName []byte, rowIndex int) (columnRowName string) {
l := len(columnName)
columnName = strconv.AppendInt(columnName, int64(rowIndex), 10)
columnRowName = string(columnName)
columnName = columnName[:l]
return
}
希望能帮到你!
0 条评论
撰写评论