当前位置:首页 > 文章列表 > Golang > Go问答 > Postgres 数组在 Golang 结构中的应用

Postgres 数组在 Golang 结构中的应用

来源:stackoverflow 2024-03-02 10:54:23 0浏览 收藏

怎么入门Golang编程?需要学习哪些知识点?这是新手们刚接触编程时常见的问题;下面golang学习网就来给大家整理分享一些知识点,希望能够给初学者一些帮助。本篇文章就来介绍《Postgres 数组在 Golang 结构中的应用》,涉及到,有需要的可以收藏一下

问题内容

我有以下 go 结构:

type bar struct {
    stuff string `db:"stuff"`
    other string `db:"other"`
}

type foo struct {
    id    int    `db:"id"`
    bars  []*bar `db:"bars"`
}

因此 foo 包含 bar 指针的切片。我在 postgres 中还有以下表格:

create table foo (
    id  int
)

create table bar (
    id      int,
    stuff   varchar,
    other   varchar,
    trash   varchar
)

我想在表 bar 上进行 left join 并将其聚合为一个数组以存储在结构 foo 中。我试过:

SELECT f.*,
ARRAY_AGG(b.stuff, b.other) AS bars
FROM foo f
LEFT JOIN bar b
ON f.id = b.id
WHERE f.id = $1
GROUP BY f.id

但看起来 array_agg 函数签名不正确(function array_agg(charactervaring,charactervarying)不存在)。有没有办法在不单独查询 bar 的情况下执行此操作?


解决方案


正如您所知,array_agg 接受一个单个参数并返回该参数类型的数组。因此,如果您希望行的所有列都包含在数组的元素中,您可以直接传递行引用,例如:

select array_agg(b) from b

但是,如果您只想在数组元素中包含特定列,则可以使用 ROW 构造函数,例如:

select array_agg(row(b.stuff, b.other)) from b

go 的标准库为仅扫描标量值提供开箱即用的支持。要扫描更复杂的值(例如任意对象和数组),必须寻找第 3 方解决方案,或实现他们自己的 sql.Scanner

为了能够实现您自己的 sql.scanner 并正确解析 postgres 行数组,您首先需要知道 postgres 使用什么格式输出值,您可以直接使用 psql 和一些查询来找到这一点: p>

-- simple values
select array[row(123,'foo'),row(456,'bar')];
-- output: {"(123,foo)","(456,bar)"}

-- not so simple values 
select array[row(1,'a b'),row(2,'a,b'),row(3,'a",b'),row(4,'(a,b)'),row(5,'"','""')];
-- output: {"(1,\"a b\")","(2,\"a,b\")","(3,\"a\"\",b\")","(4,\"(a,b)\")","(5,\"\"\"\",\"\"\"\"\"\")"}

正如你所看到的,这可能会变得非常复杂,但尽管如此,它是可以解析的,语法看起来像这样:

{"(column_value[, ...])"[, ...]}

其中 column_value 是一个不带引号的值,或者是带有转义双引号的带引号的值,并且这样的带引号的值本身可以包含转义双引号,但只能包含两个,即单个转义双引号将不会出现在 column_value 内部。因此,解析器的粗略且不完整的实现可能如下所示:

注意:可能还有其他我不知道的语法规则,在解析过程中需要考虑。除此之外,下面的代码无法正确处理 null。

func parserowarray(a []byte) (out [][]string) {
    a = a[1 : len(a)-1] // drop surrounding curlies

    for i := 0; i < len(a); i++ {
        if a[i] == '"' { // start of row element
            row := []string{}

            i += 2 // skip over current '"' and the following '('
            for j := i; j < len(a); j++ {
                if a[j] == '\\' && a[j+1] == '"' { // start of quoted column value
                    var col string // column value

                    j += 2 // skip over current '\' and following '"'
                    for k := j; k < len(a); k++ {
                        if a[k] == '\\' && a[k+1] == '"' { // end of quoted column, maybe
                            if a[k+2] == '\\' && a[k+3] == '"' { // nope, just escaped quote
                                col += string(a[j:k]) + `"`
                                k += 3    // skip over `\"\` (the k++ in the for statement will skip over the `"`)
                                j = k + 1 // skip over `\"\"`
                                continue  // go to k loop
                            } else { // yes, end of quoted column
                                col += string(a[j:k])
                                row = append(row, col)
                                j = k + 2 // skip over `\"`
                                break     // go back to j loop
                            }
                        }

                    }

                    if a[j] == ')' { // row end
                        out = append(out, row)
                        i = j + 1 // advance i to j's position and skip the potential ','
                        break     // go to back i loop
                    }
                } else { // assume non quoted column value
                    for k := j; k < len(a); k++ {
                        if a[k] == ',' || a[k] == ')' { // column value end
                            col := string(a[j:k])
                            row = append(row, col)
                            j = k // advance j to k's position
                            break // go back to j loop
                        }
                    }

                    if a[j] == ')' { // row end
                        out = append(out, row)
                        i = j + 1 // advance i to j's position and skip the potential ','
                        break     // go to back i loop
                    }
                }
            }
        }
    }
    return out
}

拨打 playground 试试。

有了类似的东西,你就可以为你的 go 条形切片实现 sql.scanner

type barlist []*bar

func (ls *barlist) scan(src interface{}) error {
    switch data := src.(type) {
    case []byte:
        a := praserowarray(data)
        res := make(barlist, len(a))
        for i := 0; i < len(a); i++ {
            bar := new(bar)
            // here i'm assuming the parser produced a slice of at least two
            // strings, if there are cases where this may not be the true you
            // should add proper length checks to avoid unnecessary panics.
            bar.stuff = a[i][0]
            bar.other = a[i][1]
            res[i] = bar
        }
        *ls = res
    }
    return nil
}

现在,如果您将 foo 类型中的 bars 字段的类型从 []*bar 更改为 barlist,您将能够直接将该字段的指针传递给 (*sql.row|*sql) .rows).scan 调用:

rows.scan(&f.bars)

如果您不想更改字段的类型,您仍然可以通过在将指针传递给 scan 方法时转换指针来使其工作:

rows.scan((*barlist)(&f.bars))

json

henry woody 建议的 json 解决方案的 sql.scanner 实现如下所示:

type barlist []*bar

func (ls *barlist) scan(src interface{}) error {
    if b, ok := src.([]byte); ok {
        return json.unmarshal(b, ls)
    }
    return nil
}

看起来你想要的是 bars 成为一个 bar 对象数组来匹配你的 go 类型。为此,您应该使用 json_agg 而不是 array_agg,因为 array_agg 仅适用于单列,并且在这种情况下会生成文本类型的数组 (text[])。另一方面,json_agg 创建一个 json 对象数组。您可以将其与 json_build_object 结合使用,以仅选择所需的列。

这是一个例子:

select f.*,
json_agg(json_build_object('stuff', b.stuff, 'other', b.other)) as bars
from foo f
left join bar b
on f.id = b.id
where f.id = $1
group by f.id

然后你必须在 go 中处理对 json 的解组,但除此之外你应该可以开始了。

另请注意,在将 json 解组到结构体时,go 会忽略未使用的键,因此如果需要,您可以通过选择 bar 表上的所有字段来简化查询。就像这样:

select f.*,
json_agg(to_json(b.*)) as bars -- or json_agg(b.*)
from foo f
left join bar b
on f.id = b.id
where f.id = $1
group by f.id

如果您还想处理 bar 中没有 foo 记录的条目的情况,您可以使用:

SELECT f.*,
COALESCE(
    JSON_AGG(TO_JSON(b.*)) FILTER (WHERE b.id IS NOT NULL),
    '[]'::JSON
) AS bars
FROM foo f
LEFT JOIN bar b
ON f.id = b.id
WHERE f.id = $1
GROUP BY f.id

如果没有 filter,您将获得 [null] ,其中 foo 中的行在 bar 中没有对应的行,而 filter 只提供 null ,然后只需使用 zqbc zqbcoalesce 转换为空 json 数组。

文中关于的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《Postgres 数组在 Golang 结构中的应用》文章吧,也可关注golang学习网公众号了解相关技术文章。

版本声明
本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
Revel 模板如何访问列表中的值Revel 模板如何访问列表中的值
上一篇
Revel 模板如何访问列表中的值
修改苹果设备的MAC地址文件
下一篇
修改苹果设备的MAC地址文件
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    508次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • 笔灵AI生成答辩PPT:高效制作学术与职场PPT的利器
    笔灵AI生成答辩PPT
    探索笔灵AI生成答辩PPT的强大功能,快速制作高质量答辩PPT。精准内容提取、多样模板匹配、数据可视化、配套自述稿生成,让您的学术和职场展示更加专业与高效。
    24次使用
  • 知网AIGC检测服务系统:精准识别学术文本中的AI生成内容
    知网AIGC检测服务系统
    知网AIGC检测服务系统,专注于检测学术文本中的疑似AI生成内容。依托知网海量高质量文献资源,结合先进的“知识增强AIGC检测技术”,系统能够从语言模式和语义逻辑两方面精准识别AI生成内容,适用于学术研究、教育和企业领域,确保文本的真实性和原创性。
    38次使用
  • AIGC检测服务:AIbiye助力确保论文原创性
    AIGC检测-Aibiye
    AIbiye官网推出的AIGC检测服务,专注于检测ChatGPT、Gemini、Claude等AIGC工具生成的文本,帮助用户确保论文的原创性和学术规范。支持txt和doc(x)格式,检测范围为论文正文,提供高准确性和便捷的用户体验。
    37次使用
  • 易笔AI论文平台:快速生成高质量学术论文的利器
    易笔AI论文
    易笔AI论文平台提供自动写作、格式校对、查重检测等功能,支持多种学术领域的论文生成。价格优惠,界面友好,操作简便,适用于学术研究者、学生及论文辅导机构。
    48次使用
  • 笔启AI论文写作平台:多类型论文生成与多语言支持
    笔启AI论文写作平台
    笔启AI论文写作平台提供多类型论文生成服务,支持多语言写作,满足学术研究者、学生和职场人士的需求。平台采用AI 4.0版本,确保论文质量和原创性,并提供查重保障和隐私保护。
    41次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码