Golangで1対多または多対多のSQL関係を扱う場合、行を構造体にマッピングする最良の(効率的で推奨される「Goのような」)方法は何ですか?
以下の設定例を使用して、私はそれぞれの長所と短所を使用していくつかのアプローチを詳しく説明しようとしましたが、コミュニティが何を推奨しているか疑問に思いました。
database/sql
とjmoiron/sqlx
のみを使用します明確にするために、エラー処理を削除しました
モデル
type Tag struct {
ID int
Name string
}
type Item struct {
ID int
Tags []Tag
}
データベース
CREATE TABLE item (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
);
CREATE TABLE tag (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(160),
item_id INT REFERENCES item(id)
);
アプローチ1-すべてのアイテムを選択してから、アイテムごとにタグを選択
var items []Item
sqlxdb.Select(&items, "SELECT * FROM item")
for i, item := range items {
var tags []Tag
sqlxdb.Select(&tags, "SELECT * FROM tag WHERE item_id = $1", item.ID)
items[i].Tags = tags
}
長所
短所
アプローチ2-SQL結合を作成し、行を手動でループする
var itemTags = make(map[int][]Tag)
var items = []Item{}
rows, _ := sqlxdb.Queryx("SELECT i.id, t.id, t.name FROM item AS i JOIN tag AS t ON t.item_id = i.id")
for rows.Next() {
var (
itemID int
tagID int
tagName string
)
rows.Scan(&itemID, &tagID, &tagName)
if tags, ok := itemTags[itemID]; ok {
itemTags[itemID] = append(tags, Tag{ID: tagID, Name: tagName,})
} else {
itemTags[itemID] = []Tag{Tag{ID: tagID, Name: tagName,}}
}
}
for itemID, tags := range itemTags {
items = append(Item{
ID: itemID,
Tags: tags,
})
}
長所
短所
失敗したアプローチ3-sqlx構造体スキャン
失敗にもかかわらず、私はこのアプローチを、開発の単純さと組み合わせた効率の私の現在の目標であると思うので、含めたいと思います。私の希望は、各構造体フィールドにdb
タグを明示的に設定することでしたsqlx
は、高度な構造体スキャンを実行できました
var items []Item
sqlxdb.Select(&items, "SELECT i.id AS item_id, t.id AS tag_id, t.name AS tag_name FROM item AS i JOIN tag AS t ON t.item_id = i.id")
残念ながら、これはmissing destination name tag_id in *[]Item
としてエラーになり、StructScan
は行を再帰的にループするほど高度ではないと信じるようになります(批判なし-複雑なシナリオです)
可能なアプローチ4-PostgreSQL配列アグリゲーターおよびGROUP BY
私はこれが動作しないと確信していますが私はそれが改善できるかどうかを確認するためにこのテストされていないオプションを含めましたmay動作します。
var items = []Item{}
sqlxdb.Select(&items, "SELECT i.id as item_id, array_agg(t.*) as tags FROM item AS i JOIN tag AS t ON t.item_id = i.id GROUP BY i.id")
時間があれば、ここでいくつかの実験を行います。
postgresのSQL:
create schema temp;
set search_path = temp;
create table item
(
id INT generated by default as identity primary key
);
create table tag
(
id INT generated by default as identity primary key,
name VARCHAR(160),
item_id INT references item (id)
);
create view item_tags as
select id,
(
select
array_to_json(array_agg(row_to_json(taglist.*))) as array_to_json
from (
select tag.name, tag.id
from tag
where item_id = item.id
) taglist ) as tags
from item ;
-- golang query this maybe
select row_to_json(row)
from (
select * from item_tags
) row;
次に、golangはこのSQLをクエリします:
select row_to_json(row)
from (
select * from item_tags
) row;
構造化するために非整列化します。
プロ:
postgresはデータの関係を管理します。 SQL関数を使用してデータを追加/更新します。
golangはビジネスモデルとロジックを管理します。
簡単な方法です。
。
以前使用した別のアプローチを提案できます。
この場合、クエリでタグのjsonを作成して返します。
長所:データを集計するdbへの呼び出しが1つあり、jsonを解析して配列に変換するだけです。
短所:少し見苦しいです。それを私に打ち明けてください。
type jointItem struct {
Item
ParsedTags string
Tags []Tag `gorm:"-"`
}
var jointItems []*jointItem
db.Raw(`SELECT
items.*,
(SELECT CONCAT(
'[',
GROUP_CONCAT(
JSON_OBJECT('id', id,
'name', name
)
),
']'
)) as parsed_tags
FROM items`).Scan(&jointItems)
for _, o := range jointItems {
var tempTags []Tag
if err := json.Unmarshall(o.ParsedTags, &tempTags) ; err != nil {
// do something
}
o.Tags = tempTags
}
編集:コードが奇妙に動作する可能性があるため、同じ構造体を使用するのではなく、移動するときに一時タグ配列を使用する方が良いと思います。