2 回答

TA貢獻(xiàn)1803條經(jīng)驗(yàn) 獲得超3個(gè)贊
看起來你想要的是bars一個(gè) bar 對(duì)象數(shù)組來匹配你的 Go 類型。為此,您應(yīng)該使用JSON_AGG而不是ARRAY_AGG因?yàn)锳RRAY_AGG僅適用于單列,并且在這種情況下會(huì)生成文本類型 ( TEXT[]) 的數(shù)組。JSON_AGG,另一方面,創(chuàng)建一個(gè) json 對(duì)象數(shù)組。您可以將其與JSON_BUILD_OBJECT僅選擇所需的列相結(jié)合。
這是一個(gè)例子:
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,但除此之外你應(yīng)該很高興。
另請(qǐng)注意,在將 json 解組為結(jié)構(gòu)時(shí),Go 會(huì)為您忽略未使用的鍵,因此您可以根據(jù)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
如果您還想處理 inbar中的記錄沒有條目的情況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]infoo中沒有相應(yīng)行的行bar,而FILTER只是給您NULL,然后只需使用它COALESCE來轉(zhuǎn)換為空的 json 數(shù)組。

TA貢獻(xiàn)1825條經(jīng)驗(yàn) 獲得超4個(gè)贊
正如您已經(jīng)知道的那樣array_agg,接受一個(gè)參數(shù)并返回參數(shù)類型的數(shù)組。因此,如果您希望所有行的列都包含在數(shù)組的元素中,您可以直接傳入行引用,例如:
SELECT array_agg(b) FROM b
但是,如果您只想在數(shù)組元素中包含特定列,則可以使用ROW構(gòu)造函數(shù),例如:
SELECT array_agg(ROW(b.stuff, b.other)) FROM b
Go 的標(biāo)準(zhǔn)庫為僅掃描標(biāo)量值提供了開箱即用的支持。要掃描更復(fù)雜的值,例如任意對(duì)象和數(shù)組,必須尋找 3rd 方解決方案,或者實(shí)現(xiàn)他們自己的sql.Scanner.
為了能夠?qū)崿F(xiàn)自己的sql.Scanner并正確解析 postgres 行數(shù)組,您首先需要知道 postgres 用于輸出值的格式,您可以通過使用psql和一些直接查詢來找到它:
-- 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,\"\"\"\",\"\"\"\"\"\")"}
正如你所看到的,這可能會(huì)變得很復(fù)雜,但它是可解析的,語法看起來是這樣的:
{"(column_value[, ...])"[, ...]}
wherecolumn_value是未加引號(hào)的值,或者是帶有轉(zhuǎn)義雙引號(hào)的引用值,并且這樣的引用值本身可以包含轉(zhuǎn)義的雙引號(hào),但只能包含兩個(gè),即單個(gè)轉(zhuǎn)義的雙引號(hào)不會(huì)出現(xiàn)在column_value. 所以解析器的粗略和不完整的實(shí)現(xiàn)可能看起來像這樣:
注意:在解析過程中可能需要考慮其他我不知道的語法規(guī)則。除此之外,下面的代碼不能正確處理 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。
有了類似的東西,您就可sql.Scanner以為您的 Go 條形圖實(shí)現(xiàn)一個(gè)。
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
}
現(xiàn)在,如果您將類型中的Bars字段Foo類型從[]*Bar更改為,BarList您將能夠直接將字段的指針傳遞給(*sql.Row|*sql.Rows).Scan調(diào)用:
rows.Scan(&f.Bars)
如果您不想更改字段的類型,您仍然可以通過在將指針傳遞給Scan方法時(shí)轉(zhuǎn)換指針來使其工作:
rows.Scan((*BarList)(&f.Bars))
JSON
sql.ScannerHenry Woody 建議的 json 解決方案的實(shí)現(xiàn)如下所示:
type BarList []*Bar
func (ls *BarList) Scan(src interface{}) error {
if b, ok := src.([]byte); ok {
return json.Unmarshal(b, ls)
}
return nil
}
- 2 回答
- 0 關(guān)注
- 235 瀏覽
添加回答
舉報(bào)