我正在从多个表构建分层 JSON 结果。这些只是示例,但对于本演示的目的来说应该足以理解这个想法:
CREATE TABLE book (
id INTEGER PRIMARY KEY NOT NULL,
data JSONB
);
CREATE TABLE author (
id INTEGER PRIMARY KEY NOT NULL,
data JSONB
);
CREATE TABLE book_author (
id INTEGER PRIMARY KEY NOT NULL,
author_id INTEGER,
book_id INTEGER
);
CREATE UNIQUE INDEX pk_unique ON book_author (author_id, book_id);
测试数据:
INSERT INTO book (id, data) VALUES
(1, '{"pages": 432, "title": "2001: A Space Odyssey"}')
, (2, '{"pages": 300, "title": "The City And The City"}')
, (3, '{"pages": 143, "title": "Unknown Book"}');
INSERT INTO author (id, data) VALUES
(1, '{"age": 90, "name": "Arthur C. Clarke"}')
, (2, '{"age": 43, "name": "China Miéville"}');
INSERT INTO book_author (id, author_id, book_id) VALUES
(1, 1, 1)
, (2, 1, 2);
我创建了以下功能:
CREATE OR REPLACE FUNCTION public.book_get()
RETURNS json AS
$BODY$
DECLARE
result json;
BEGIN
SELECT to_json(array_agg(_b)) INTO result
FROM (
SELECT
book.id id,
book.data->>'title' title,
book.data->>'pages' pages,
(
SELECT to_json(array_agg(_a))
FROM (
SELECT
author.id id,
author.data->>'name' "name",
author.data->>'age' age
FROM
author, book_author ba
WHERE
ba.author_id = author.id AND
ba.book_id = book.id
ORDER BY id
) _a
) authors
FROM
book
ORDER BY id ASC
) _b;
RETURN result;
END;
$BODY$ LANGUAGE plpgsql VOLATILE;
执行函数book_get
SELECT book_get();
产生以下结果
[
{
"id":1,
"title":"2001: A Space Odyssey",
"pages":432,
"authors":[
{
"id":1,
"name":"Arthur C. Clarke",
"age":90
}
]
},
{
"id":2,
"title":"The City And The City",
"pages":300,
"authors":[
{
"id":2,
"name":"China Miéville",
"age":43
}
]
},
{
"id":3,
"title":"Unknown Book",
"pages":143,
"authors":null
}
]
现在我可以用一个WHERE
子句过滤数据,例如
SELECT to_json(array_agg(_b)) INTO result
FROM (
...
) _b
-- give me the book with id 1
WHERE _b.id = 1;
-- or give me all titles with the occurrence of 'City' anywhere
WHERE _b.title LIKE '%City%';
-- or has more than 200 pages
WHERE _b.pages > 200;
我如何才能过滤authors
?例如,等同于WHERE _b.authors.'name' = 'Arthur C. Clarke'
.
我完全不知道会authors
变成什么类型?或者是?它仍然是一个记录(数组)吗?已经是 JSON 了吗?我猜是因为我可以访问id
,访问不是这样的问题吗title
?pages
_b.authors
访问_b.authors
给了我ERROR: missing FROM-clause entry for table "authors"
使用 JSON 运算符访问_b.authors->>..
或_b->authors->>..
给我
operator does not exist: record -> json Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
我记得使用GROUP BY
withHAVING
子句:
GROUP BY _b.authors
HAVING _b.authors->>'name' = 'Arthur C. Clarke';
但这给了我错误:
错误:无法识别 json 类型的相等运算符
为了更清楚一点:
SELECT to_json(array_agg(_b)) INTO result
FROM (
...
) _b
WHERE _b.authors->0->>'name' = 'Arthur C. Clarke';
基本上会做我需要的,这只有在索引上的作者0
是Arthur C. Clarke
. 如果他与人合写了这本书并且他会排在第二位(索引 1),那么就不会有比赛了。所以我试图找到的是正确的扫描语法,_b.authors
它恰好是一个由作者填充的 JSON 数组。它只是不接受任何尝试。据我了解@>
,#>
仅支持JSONB
. 那么如何在_b.authors
针对值的任何列上进行选择时获得正确的语法。
更新 2
好的,再次阅读文档......似乎我没有从 Postgres 文档中得到关于 JSON 和 JSONB 在函数方面存在差异的部分,我认为这仅与数据类型有关。在where子句中使用诸如 etc 之to_json
类的to_jsonb
运算符似乎可以解决问题。@>
更新 3
@ErwinBrandstetter:有道理。LATERAL 我还不知道,很高兴知道它的存在。我掌握了 JSON/JSONB 的函数和运算符,现在对我来说很有意义。我不清楚的是在子句中找到LIKE
例如出现的情况。WHERE
如果我需要jsonb_array_elements
在数组中取消嵌套对象(因为在最后的WHERE
子句中,内容b.authors
是 JSONB 数据类型)。然后我可以做
SELECT * FROM jsonb_array_elements('[
{"age": 90, "name": "the Arthur C. Clarke"},
{"age": 43, "name": "China Miéville"},
{"age": null, "name": "Erwin the Brandstetter"}
]'::jsonb) author
WHERE
author->>'name' LIKE '%the%';
并得到想要的结果,
1: {"age": 90, "name": "the Arthur C. Clarke"}
2: {"age": null, "name": "Erwin the Brandstetter"}
WHERE
但是在我的示例中,在最后(最后)子句中实现这一目标的方法是什么?指出最后一个WHERE
子句,因为我想过滤完整的结果集,而不是在子选择中间的某个地方部分过滤。所以总的来说,我想在最终结果集中过滤掉作者中间名为“C”的书籍。或名字“亚瑟”。
更新 4
当然是在FROM
条款中。当我找出所有可能性时,我将不得不在最后进行性能调整,但这就是我想出的。
SELECT json_agg(_b) INTO result
FROM (
...
) _b,
jsonb_array_elements(_b.authors) AS arrauthors
WHERE arrauthors->>'name' LIKE 'Arthur %';
将给出作者姓名以“亚瑟”开头的所有书籍。我仍然感谢对这种方法的评论或更新。