golang是强类型语言,在赋值和解析过程中需要先定义好数据类型,否在会报类型错误,下面总结在处理数据库表时遇到字段为空或零值的情况

场景

假如存在如下没有指定not null的场合

1
2
3
4
5
CREATE TABLE "users" (
    "id" serial not null primary key,
    "name" text,
    "age" integer
)

在gorp中insert插入场合,可以直接赋零值即可,很方便.

1
2
3
4
5
6
dbmap.Insert(
 &User{Name: "John", Age: 0}, // insert into "users" ("id","name","age") values (default,$1,$2) returning Id; [1:"John" 2:0]
 &User{Name: "John"}, // insert into "users" ("id","name","age") values (default,$1,$2) returning Id; [1:"John" 2:0]
 &User{Name: "", Age: 8}, // insert into "users" ("id","name","age") values (default,$1,$2) returning Id; [1:"" 2:8]
 &User{Age: 30}, // insert into "users" ("id","name","age") values (default,$1,$2) returning Id; [1:"" 2:30]
)

在使用golang中零值与空值和NULL是不同的数据类型和值,需要加以判断,在数据库表中的NULL值字段可以用database/sql数据包中提供的sql.NullString,sql.NullBool等值类型进行判断后加以使用.

如何使用

可能存在NULL值的数据类型可以使用sql.NullString或sql.NullBool等来指定其类型.

1
2
3
4
5
type User struct{
    Id int `db:id`
    Name sql.NullString `db:name`
    Age sql.NullInt64 `db:age`
}

其中sql.NullString,它的结构如下:

1
2
3
4
type NullString struct {
    String string
    Valid  bool // Valid is true if String is not NULL
}

借助.sql.NullString这样的结构体就可以在insert时,通过设置Valid的值为fasle就可以表示此值为null值,这样在读取时如果为false就可以肯定此值为默认的空值了,

具体操作如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
dbmap.Insert(
    &User{
      Name: sql.NullString{"Mike", true},
      Age: sql.NullInt64{0, true},
    }, //  insert into "users" ("id","name","age") values (default,$1,$2) returning Id; [1:"Mike" 2:0]
    &User{
      Name: sql.NullString{"", false},
      Age: sql.NullInt64{30, true},
    }, // insert into "users" ("id","name","age") values (default,$1,$2) returning Id; [1:<nil> 2:30]
  )

读取数据时,可以根据valid的值判断是否为设置的零值还是未被赋值操作.

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
dbmap.Insert(
    &User{Name: sql.NullString{"Mike", true}, Age: sql.NullInt64{0, true}},
    &User{Name: sql.NullString{"John", true}, Age: sql.NullInt64{0, false}},
    &User{Name: sql.NullString{"John", true}, Age: sql.NullInt64{8, true}},
    &User{Name: sql.NullString{"", false}, Age: sql.NullInt64{30, true}},
  )

  users := []User{}
  _, err := dbmap.Select(&users, "select * from users")
  if err != nil {
    log.Fatal(err)
  }
  for _, user := range users {
    spew.Dump(user)
  }
/*
(main.User) {
 Id: (int) 1,
 Name: (sql.NullString) {
  String: (string) (len=4) "Mike",
  Valid: (bool) true
 },
 Age: (sql.NullInt64) {
  Int64: (int64) 0,
  Valid: (bool) true
 }
}
(main.User) {
 Id: (int) 2,
 Name: (sql.NullString) {
  String: (string) (len=4) "John",
  Valid: (bool) true
 },
 Age: (sql.NullInt64) {
  Int64: (int64) 0,
  Valid: (bool) false
 }
}
(main.User) {
 Id: (int) 3,
 Name: (sql.NullString) {
  String: (string) (len=4) "John",
  Valid: (bool) true
 },
 Age: (sql.NullInt64) {
  Int64: (int64) 8,
  Valid: (bool) true
 }
}
(main.User) {
 Id: (int) 4,
 Name: (sql.NullString) {
  String: (string) "",
  Valid: (bool) false
 },
 Age: (sql.NullInt64) {
  Int64: (int64) 30,
  Valid: (bool) true
 }
}
*/

在单次查询中根据valid的值作相应的零值判断处理, 方法如下:

1
2
3
4
5
6
7
8
var s NullString
err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
...
if s.Valid {
   // use s.String
} else {
   // NULL value
}

空值处理

数据库有一个特殊的类型,NULL空值。可是NULL不能通过scan直接跟普遍变量赋值,甚至也不能将null赋值给nil。对于null必须指定特殊的类型,这些类型定义在database/sql库中。例如sql.NullFloat64。如果在标准库中找不到匹配的类型,可以尝试在驱动中寻找。下面是一个简单的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var (
    s1 string
    s2 sql.NullString
    i1 int
    f1 float64
    f2 float64
)
// 假设数据库的记录为 ["hello", NULL, 12345, "12345.6789", "not-a-float"]
err = rows.Scan(&s1, &s2, &i1, &f1, &f2) if err != nil {
log.Fatal(err) }

因为最后一个f2字段的值不是float,这会引发一个错误。

sql: Scan error on column index 4: converting string "not-a- oat" to a  oat64: strconv.ParseFloat: parsing "not-a- oat": invalid syntax

如果忽略err,强行读取目标变量,可以看到最后一个值转换错误会处理,而不是抛出异常:

1
2
3
4
err = rows.Scan(&s1, &s2, &i1, &f1, &f2)
log.Printf("%q %#v %d %f %f", s1, s2, i1, f1, f2)

// 输出 "hello"  sql.NullString{String:"", Valid:false} 12345 12345.678900 0.000000

可以看到,除了最后一个转换失败变成了零值之外,其他都正常的取出了值,尤其是null匹配了NullString类型的目标变量。

对于null的操作,通常仍然需要验证:

1
2
3
4
5
6
7
8
9
var world sql.NullString
err := db.QueryRow("SELECT world FROM hello WHERE id = ?", id).Scan(&world)
...
if world.Valid {
      wrold.String
} else {
    // 数据库的value是不是null的时候,输出 world的字符串值, 空字符串
    world.String
}

对应的,如果world字段是一个int,那么声明的目标变量类似是sql.NullInt64,读取其值的方法为world.Int64。

但是有时候我们并不关心值是不是Null,我们只需要把他当一个空字符串来对待就行。这时候我们可以使用[]byte(null byte[]可以转化为空string)

1
2
3
4
var world []byte
err := db.QueryRow("SELECT world FROM hello WHERE id = ?", id).Scan(&world)
...
log.Println(string(real_name)) // 有值则取出字串值,null则转换成 空字串。