LoginSignup
27

More than 5 years have passed since last update.

テーブルのカラムの値をnullかゼロ値かを判別する

Posted at

not nullが指定されていないテーブルがあった場合の話。

CREATE TABLE "users" (
  "id" serial not null primary key,
  "name" text,
  "age" integer
)

gorpで単純にinsertすると、ゼロ値が入っていまいます。

  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]
  )

go言語はゼロ値があるので、空文字なのかNULLなのかが判別できないのが原因です。
これに対応するために、database/sqlパッケージには、null値を扱うために
sql.NullStringsql.NullBoolなどが用意されています。

使い方

nullの可能性のある型にsql.NullStringなどを指定。

type User struct {
  Id   int            `db:id`
  Name sql.NullString `db:name`
  Age  sql.NullInt64  `db:age`
}

このsql.NullStringなどは、こんな構造体になってます。

type NullString struct {
        String string
        Valid  bool // Valid is true if String is not NULL
}

Insertを呼び出す前に、Validの値をfalseにするとnullとしてINSERTされるようになります。

  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の値をみることになります。

  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
 }
}
*/

ちなみにこれ、普通にScanをするときも使えます。

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27