我正在处理 Go 中的多对多关系。为此,我使用的是pgx
PostgreSQL 驱动程序。
为了使这个问题尽可能简单,让我们假设一个简单的博客文章可以有一些标签:
CREATE TABLE IF NOT EXISTS tag (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
tagName varchar(255) UNIQUE NOT NULL,
);
CREATE TABLE IF NOT EXISTS post (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
title varchar(255) NOT NULL,
description varchar(255),
);
CREATE TABLE IF NOT EXISTS post_tag (
post_id bigint REFERENCES post (id) ON UPDATE CASCADE ON DELETE CASCADE,
tag_id bigint REFERENCES tag (id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT post_tag_pkey PRIMARY KEY (post_id, tag_id)
);
为了检索帖子及其标签,我使用了类似于以下查询的内容(为了在 Go 中轻松查询):
SELECT p.id as post_id, p.title, p.description, t.id as tag_id, t.tagName as tag_name
FROM post p
LEFT JOIN post_tag pt
ON p.id = pt.post_id
LEFT JOIN tag t
ON t.id = pt.tag_id;
此查询可能会返回一些行,其中tag_id
和tag_name
为空。我目前处理此问题的方式如下(为简单起见,删除了错误处理):
func ReadPosts() ([]*model.Post, error) {
var posts []*model.Post
var postWithTags = make(map[uint64]*model.Post)
statement := `SELECT *
FROM post_with_tag` // pgsql view with joins to get tags
rows, _ := db.Query(
context.Background(),
statement,
)
for rows.Next() {
var (
post model.Post
tag model.Tag
tagID pgtype.Numeric
tagName pgtype.Varchar
tagValid bool
)
_ = rows.Scan(
&post.ID,
&post.Title,
&post.Description,
&tagID,
&tagName,
)
if tagID.Status == pgtype.Present {
tag.ID = tagID.Int.Uint64()
tag.Name = tagName.String
tagValid = true
} else {
tagValid = false
}
if _, ok := postWithTags[post.ID]; ok {
if tagValid {
postWithTags[post.ID].Tags = append(postWithTags[post.ID].Tags, &tag)
}
} else {
post.Tags = []*model.Tag{}
if tagValid {
post.Tags = []*model.Tag{
&tag,
}
}
postWithTags[post.ID] = &post
}
}
for _, v := range postWithTags {
posts = append(posts, v)
}
return posts, nil
}
如您所见,我正在使用pgtype
来处理潜在的空值。我应该提到这个解决方案有效。但是,我有两个问题:
- 这个解决方案看起来相当笨重和凌乱;阅读起来很复杂(至少对我而言)。有没有更好、更惯用的 Go 方式来做到这一点?
- 调用时,
tagID.Int.Uint64()
我总是0
以标签 ID 的形式返回,这是不正确的。我在这里做错了吗?(我使用pgtype.Numeric
是因为数据库中的标签 ID 是 pgsqlbigint
)。