前提・実現したいこと
Many2Many 間で 逆参照を行い And/Or で検索結果を取得したい
2つのモデルがあります
※実現したいことに不要なFieldは全て取り除いています
go
type Problem struct { gorm.Model Title string `json:"title" binding:"required"` Tags []Tag `gorm:"many2many:problem_tags;"` } type Tag struct { gorm.Model Name string `json:"name" binding:"required"` Problems []Problem `gorm:"many2many:problem_tags;"` }
Tag.Name
が一つ / 複数与えられた時に
それらのTag.Nameを持つ Problemをすべて And/Or 検索して取得したいです
該当のソースコード
HTMLのForm から http://localhost:8080/?q=gin+gorm
のようなクエリを飛ばして
c.Request.URL.Query()
から query params を取得して
このparamsを用いて Problems を取得し表示
※paramsの取得は出来ています
※router.go, db.go などは省略します
go
func Index(c *gin.Context) { // ここで query parameters を取得 // params に入っているのは []string です params, err := utils.URLQueryParse() // err処理は省略 // !!ここ!! output := []models.Problem{} found := map[uint]bool{} for _, tagname := range params { var tag models.Tag _ = tag.GetOneByName(tagname) for _, p := range tag.Problems { if _, ok := found[p.ID]; !ok { output = append(output, p) found[p.ID] = true } } } c.HTML(http.StatusOK, "Index.tmpl", gin.H{ "Title": "Index", "Result": params, "Problems": output, }) }
試したこと
上記のように無理やり実装はしました
一応動作はします
tag.GetOneByName
は以下のようになっています
go
// func (self *Tag)GetOneByName(tagName string) error // tagName に部分一致する、Tag.Nameを持つ最初の models.Tag(ID昇順で) レコードを取得する // 見つかったら nil, 一件も見つからなかったら "record not found" を吐く // Problems も全部取得する ので処理遅い はず func (self *Tag)GetOneByName(tagName string) error { return db.DB.Preload("Problems").Where("name LIKE ?", "%"+tagName+"%").First(self).Error }
一応And検索も無理やり実装はしました
が、O(N^3)
位のアルゴリズムになっています
gorm.io
の公式Referenceを見て Back-Referece
を発見したので
これは行けるだろうと思ってさんざん弄ったのですが、結局良くわかりませんでした
普通に考えると return db.DB.Model( ... ).Where( ... ).Association( ... ).Find( ... )
のような一行のSQLで取得出来ると思うのですが、可能でしょうか
生のSQLでの解消なども可能であればお願いします
補足情報(FW/ツールのバージョンなど)
bash
❯ go version go version go1.16.2 linux/amd64 ❯ bat go go.mod go 1.16 require ( github.com/gin-gonic/gin v1.6.3 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/k0kubun/pp v3.0.1+incompatible github.com/mattn/go-colorable v0.1.8 // indirect golang.org/x/sys v0.0.0-20210324051608-47abb6519492 // indirect gorm.io/driver/sqlite v1.1.4 gorm.io/gorm v1.21.3 )
schemeは以下のとおりです
bash
❯ sqlite3 test.db SQLite version 3.31.1 2020-01-27 19:55:54 Enter ".help" for usage hints. sqlite> .tables problem_tags problems solves tags sqlite> sqlite> sqlite> .schema problems CREATE TABLE `problems` (`id` integer,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`title` text,`statement` text,`answer` text,`url` text,PRIMARY KEY (`id`)); CREATE INDEX `idx_problems_deleted_at` ON `problems`(`deleted_at`); sqlite> sqlite> sqlite> .schema tags CREATE TABLE `tags` (`id` integer,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`name` text,PRIMARY KEY (`id`)); CREATE INDEX `idx_tags_deleted_at` ON `tags`(`deleted_at`); sqlite> sqlite> sqlite> .schema problem_tags CREATE TABLE `problem_tags` (`tag_id` integer,`problem_id` integer,PRIMARY KEY (`tag_id`,`problem_id`),CONSTRAINT `fk_problem_tags_tag` FOREIGN KEY (`tag_id`) REFERENCES `tags`(`id`),CONSTRAINT `fk_problem_tags_problem` FOREIGN KEY (`problem_id`) REFERENCES `problems`(`id`));
まだ回答がついていません
会員登録して回答してみよう