[Go言語] gormのクエリ条件をイテレーション内で指定する際は結果の格納先を初期化するべきという話
GoのORMライブラリgorm(http://doc.gorm.io/)でqueryをしたときの挙動でハマった.
コードは次の通り.
package main import ( "fmt" "log" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/postgres" ) type Employee struct { gorm.Model Num string LN string FN string } func main() { connStr := "postgres://example:example@localhost:5432/example?sslmode=disable" db, err := gorm.Open("postgres", connStr) if err != nil { log.Fatal(err) } defer db.Close() // db.LogMode(true) db.AutoMigrate(&Employee{}) emps := []Employee{ Employee{Num: "001", LN: "ichi", FN: "taro"}, Employee{Num: "002", LN: "ninomiya", FN: "kinjiro"}, Employee{Num: "003", LN: "minowa", FN: "akihiro"}, Employee{Num: "004", LN: "yon", FN: "jun"}, Employee{Num: "005", LN: "godaigo", FN: "tenno"}, } for _, e := range emps { db.NewRecord(e) db.Create(&e) } nums := []string{"001", "004", "005"} var emp Employee for _, n := range nums { notFound := db.Where("num = ?", n).First(&emp).RecordNotFound() if notFound { continue } fmt.Printf("%s %s\n", emp.LN, emp.FN) } return }
nums
スライス内に記載した従業員番号に該当する従業員の名前を表示するというもの.
狙いとしては
ichi taro yon jun godaigo tenno
という結果が欲しかったのだが, 実際は次の通り, 初めの一人しかヒットしていない.
ichi taro
db.LogMode(true)
のコメントを外してログを見てみると, nums
のイテレーションにより, 次のクエリが発行されていた.
SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND ((num = '001')) ORDER BY "employees"."id" ASC LIMIT 1 SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND "employees"."id" = '1' AND ((num = '004')) ORDER BY "employees"."id" ASC LIMIT 1 SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND "employees"."id" = '1' AND ((num = '005')) ORDER BY "employees"."id" ASC LIMIT 1
どうやら, 従業員番号"001"でヒットしたユーザ(id=1)データが
&emp
ポインタに残っていると, これを引数にとるFirst
メソッド
はid=1のユーザの中から探してしまうようだ.
id=1かつ従業員番号"004"の従業員はいないので, スルーされてしまう.
このFirst
メソッドは, プライマリキー順にサーチし, 該当の条件に一致する列を返す.
実装は次の通り.
// First find first record that match given conditions, order by primary key func (s *DB) First(out interface{}, where ...interface{}) *DB { newScope := s.NewScope(out) newScope.Search.Limit(1) return newScope.Set("gorm:order_by_primary_key", "ASC"). inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db }
s.parent.callbacks.queries
をコールしてWHERE句へ条件を追加していき, 結果DB
への参照を返す.
イテレーションごとに, クエリ結果を格納する変数(ポインタ)を初期化してやれば, クエリ条件も初期化され, 望みの結果が得られた.
次のように, emp
の宣言をfor
ループの内部で行うよう修正.
nums := []string{"001", "004", "005"} // var emp Employee for _, n := range nums { var emp Employee notFound := db.Where("num = ?", n).First(&emp).RecordNotFound() if notFound { continue } ...
実行されたクエリ
SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND ((num = '001')) ORDER BY "employees"."id" ASC LIMIT 1 SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND ((num = '004')) ORDER BY "employees"."id" ASC LIMIT 1 SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND ((num = '005')) ORDER BY "employees"."id" ASC LIMIT 1
結果
ichi taro yon jun godaigo tenno