MongoDBの Offical Go言語 Driverを使ってみる（３）Find

「MongoDBの Offical Go言語 Driverを使ってみる」の3回目の投稿Find編です


# Find用のドキュメントのInsert

## 平成31年地価公示（東京都分） 公示価格個別地点データ(CSV)をダウンロード
次のURLからMongoDBにInsert用のCSVデータをダウンロードします。

http://opendata-catalogue.metro.tokyo.jp/dataset/t000004d0000000004/resource/6fdcbf0c-da44-42fb-8fa4-4dc325f5db83

## 地価公示データをMongoDBに登録

- ダウンロードしたデータは**SJIS**なので**golang.org/x/text/encoding/japanese**を使って文字コード変換します
- 英文字の項目名を考えるのが面倒なので日本語にする
    - 項目名の先頭文字が大文字の英字でないとMongoDBにInsertされないので、**F**を付けて、MongoDB上の項目名はタグに指定します
- MongoDBにInsertする項目は**都道府県市区町村コード**から**地積**までとします
- 価格などは数値項目ですが","が使われているので","を取り除く処理をしています
- Insertは**UseSession**を使ってトランザクション処理としました
- **insertData**のタイプは **[]地価公示**としないこと
- Insert件数は2603件でした

```go:insert.go
package main

import (
	"context"
	"encoding/csv"
	"io"
	"log"
	"os"
	"strconv"
	"strings"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/transform"
)

type 地価公示 struct {
	Fコード     int     "コード"
	F対象年     int     "対象年"
	F標準地名    string  "標準地名"
	F標準地番号用途 int     "標準地番号用途"
	F標準地番号連番 int     "標準地番号連番"
	F区市町村名   string  "区市町村名"
	F地番      string  "地番"
	F住居表示    string  "住居表示"
	F当年価格    int     "当年価格"
	F前年価格    int     "前年価格"
	F変動率     float64 "変動率"
	F地積      int     "地積"
}

func toInt(data string) (int, error) {
	intData := strings.Trim(strings.ReplaceAll(data, ",", ""), " ")
	return strconv.Atoi(intData)
}

func toFloat(data string) (float64, error) {
	floatData := strings.Trim(strings.ReplaceAll(data, ",", ""), " ")
	return strconv.ParseFloat(floatData, 64)
}

func mainMain() error {
	client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
	if err != nil {
		return err
	}
	if err = client.Connect(context.Background()); err != nil {
		return err
	}
	defer client.Disconnect(context.Background())
	col := client.Database("test").Collection("col")
	csvFile, err := os.Open("./31kouji_chiten_data.csv")
	if err != nil {
		log.Fatal(err)
	}
	defer csvFile.Close()
	reader := csv.NewReader(transform.NewReader(csvFile, japanese.ShiftJIS.NewDecoder()))
	//ヘッダー部の読み飛ばし
	reader.Read()
	reader.Read()
	var insertData []interface{}
	for {
		rec, err := reader.Read()
		if err == io.EOF {
			break
		} else if err != nil {
			return err
		}
		var csvData 地価公示
		csvData.Fコード, _ = toInt(rec[1])
		csvData.F対象年, _ = toInt(rec[2])
		csvData.F標準地名 = rec[3]
		csvData.F標準地番号用途, _ = toInt(rec[4])
		csvData.F標準地番号連番, _ = toInt(rec[5])
		csvData.F区市町村名 = rec[6]
		csvData.F地番 = rec[7]
		csvData.F住居表示 = rec[8]
		csvData.F当年価格, _ = toInt(rec[9])
		csvData.F前年価格, _ = toInt(rec[10])
		csvData.F変動率, _ = toFloat(rec[11])
		csvData.F地積, _ = toInt(rec[12])
		insertData = append(insertData, csvData)
	}
	err = client.UseSession(context.Background(),
		func(ctx mongo.SessionContext) error {
			err := ctx.StartTransaction()
			if err != nil {
				return err
			}
			_, err = col.InsertMany(ctx, insertData)
			if err != nil {
				return err
			}
			return ctx.CommitTransaction(ctx)
		})
	return err
}
func main() {
	if err := mainMain(); err != nil {
		log.Fatal(err)
	}
	log.Println("normal end.")
}
```

# 地価公示データのFind

## Find書式
**Find**は検索条件にマッチしたすべてのドキュメントを取得します

```go
Find(context.Context, 検索条件, Find オプション)
```

オプションは**options.Find()**で前回使用した**options.FindOne()**ではありません。

## Findのプログラム例

検索条件として**当年価格**が1000万円以上で**変動率**が高い5件を検索する例です

```javascript:mongoシェル
db.col.find({"当年価格":{$gte:10000000}},{_id:0}).sort({"変動率":-1}).limit(5)
```

- **cur.Next()**は次のドキュメントの読み込み、ドキュメントが無いときは**false**を返す
- **cur.Decode()**は読み込んだドキュメントを取得します。**Decode**できるタイプはいろいろありますので前回の投稿のFindOne編をご覧ください

```go:find.go
package main

import (
	"context"
	"fmt"
	"log"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type 地価公示 struct {
	Fコード     int     "コード"
	F対象年     int     "対象年"
	F標準地名    string  "標準地名"
	F標準地番号用途 int     "標準地番号用途"
	F標準地番号連番 int     "標準地番号連番"
	F区市町村名   string  "区市町村名"
	F地番      string  "地番"
	F住居表示    string  "住居表示"
	F当年価格    int     "当年価格"
	F前年価格    int     "前年価格"
	F変動率     float64 "変動率"
	F地積      int     "地積"
}

func mainMain() error {
	client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
	if err != nil {
		return err
	}
	if err = client.Connect(context.Background()); err != nil {
		return err
	}
	defer client.Disconnect(context.Background())
	col := client.Database("test").Collection("col")
	findOptions := options.Find().SetProjection(bson.D{{"_id", 0}})
	findOptions.SetLimit(5).SetSort(bson.D{{"変動率", -1}})
	filter := bson.D{{"当年価格", bson.D{{"$gte", 1000 * 10000}}}}
	cur, err := col.Find(context.Background(), filter, findOptions)
	if err != nil {
		return err
	}

	for cur.Next(context.Background()) {
		var doc 地価公示
		if err = cur.Decode(&doc); err != nil {
			return err
		}
		//fmt.Printf("%+v\n", doc)
		fmt.Printf("変動率:%g%%, 当年価格:%d万円, 地番:%s\n", doc.F変動率, doc.F当年価格/10000, doc.F地番)
	}
	return nil
}

func main() {
	if err := mainMain(); err != nil {
		log.Fatal(err)
	}
	log.Println("normal end.")
}
```

### 上記と同じ条件でAggregateを使ったプログラミング例

```javascript:mongoシェル
db.col.aggregate([
  {$match:{"当年価格":{$gte:10000000}}},
  {$sort:{"変動率":-1}},
  {$limit: 5},
  {$project:{_id:0}}
])
```


```go:go
	filter := bson.D{{"当年価格", bson.D{{"$gte", 1000 * 10000}}}}
	pipeline := bson.A{
		bson.D{{"$match", filter}},
		bson.D{{"$sort", bson.D{{"変動率", -1}}}},
		bson.D{{"$limit", 5}},
		bson.D{{"$project", bson.D{{"_id", 0}}}},
	}
	cur, err := col.Aggregate(context.Background(), pipeline)
```