我有一个
product-image (1:M)
关系,基本上我想将这些图像映射到我的结构中的一个切片。我正在使用 sqlx 库来使其更容易一些。
我搜索了一段时间,也许最好的答案就在这个帖子中:Efficiently地将一对多多对多数据库映射到Golang中的结构。 创建视图并将所有内容作为 json 返回的答案是可行的,但感觉有点hacky。
理想情况下,我想要做的是使用 postgres
json_agg
返回图像列中与我的类型匹配的 json 数组,以便我可以扫描它。
我用nodejs做了几次,但这里我不知道怎么做。我是否需要实现自定义
Scan
和 Value
方法,或者是否有更简单的方法。我没有展示这一点,但我也有一个 1:1 的类别,我可以将该类别嵌入产品中并执行左连接,它适用于 sqlx,但不适用于图像类型。
简化模型
type Image struct {
ID int,
URL string,
ProductID int
}
type ImageList []*Image
type Product struct {
ID int `db:"id"`
Name string `db:"name"`
Images ImageList `db:"images"`
}
数据库表
create table product (
id int generated always as identity primary key,
name varchar(255),
);
create table product_image (
id int generated always as identity primary key,
url text not null,
product_id int references product(id)
);
我现在正在尝试这样的事情:
q := `SELECT
p.*,
COALESCE(JSON_AGG(img.*) FILTER (WHERE img.product_id IS NOT NULL), '[]'::json) AS images
FROM product p
LEFT JOIN product_image img ON p.id = img.product_id
WHERE p.id = 1
GROUP BY p.id`
var p Product
if err := sqlxdb.Get(&p, q); err != nil {
fmt.Printf("Error: %v\n", err)
}
我收到此错误:
sql: Scan error on column index 26, name "images": unsupported Scan, storing driver.Value type []uint8 into type *model.ImageList
这似乎是一个非常常见的场景,但我找不到任何例子...... 或者最后我什至愚蠢地这样做,因为我可以在 JS 中执行 forEach 并执行 50 个查询来获取每个产品的所有图像。
可以使用此映射库来完成获取项目列表的一种解决方案:carta
q := `SELECT p.*, img.id AS img_id, img.url AS img_url
FROM public.product p
LEFT JOIN product_image img ON p.id = img.product_id`
rows, err := sqlxdb.Query(q)
if err != nil {
fmt.Println(err)
}
var products []*model.Product
carta.Map(rows, &products)
对于 img 结构,我将使用
db:"img_id
” 前缀等,因为我使用别名进行选择...
你的问题是你正在返回一个 json 数据的 postgres 数组。对于标准扫描代码来说,这看起来像一个 []uint8。这并不像评论中提到的将扫描仪接口添加到数组那么简单。 Postgres 有一个特定的文本格式来传递需要正确解析的数组。
查看此代码以了解如何正确解析 Postgres 数组列。 https://github.com/lib/pq/blob/master/array.go
这是我整理的一个示例(基于我最近使用的一些代码)来演示解决您的问题的一种可能方法。我确信它不是最佳的,但它在本地测试中有效。
type PqArrayOfJson[T any] []T
func (a *PqArrayOfJson[T]) Scan(src interface{}) error {
raw := &pq.ByteaArray{}
if err := raw.Scan(src); err != nil {
return err
}
parsed := make(PqArrayOfJson[T], len(*raw))
for i, v := range *raw {
nv := *new(T)
if err := json.Unmarshal(v, &nv); err != nil {
return err
}
parsed[i] = nv
}
*a = parsed
return nil
}
func (a PqArrayOfJson[T]) Value() (driver.Value, error) {
mapped := make([][]byte, len(a))
for i, v := range a {
data, err := json.Marshal(&v)
if err != nil {
return nil, err
}
mapped[i] = data
}
return pq.ByteaArray(mapped).Value()
}
然后你会使用这样的东西
type ImageList = PqArrayOfJson[*Image]
这是使用 pq lib 将传入的 json 数据数组解析为
[][]byte
,然后使用泛型类型 json.Unmarshal
将其中的每个元素输入 T
,以创建一个要解组的新变量。
如果您将此作为一个想法,您可以创建专用的
ImageList
类型并忽略代码的所有通用方面。